Lesson 5 of 25 · Getting started

Strings and text

Strings hold text, and Salam is Unicode from the ground up, so text in any script behaves like any other text. The one thing to keep in mind is that a string is stored as UTF-8 bytes, and some operations count bytes while others count characters.

Ways to write a string

  • "..." is an ordinary string. Escapes work inside it: \n (a new line), \t (a tab), \", \\, and \uHHHH for any Unicode character by its code.
  • `...` (backticks) is a raw string: everything inside stays exactly as written, backslashes and quotes included, and it may span several lines. It is the easy way to write file paths, regular expressions and JSON.
  • """...""" is a multi-line string in which escapes still work.
str-literals.salam
func main:
    println "first line\nsecond line"
    println "column\tcolumn"
    println "She said: \"hello\""
    println "price in \u20ac, greeting \u0633\u0644\u0627\u0645"
    println `the path C:\temp and "quotes", with nothing escaped`
    poem := """The children of Adam are limbs of one body,
having been created of one essence."""
    println poem
end
Outputfirst line second line column column She said: "hello" price in €, greeting سلام the path C:\temp and "quotes", with nothing escaped The children of Adam are limbs of one body, having been created of one essence.

An ordinary "..." string has to end on the line where it starts; for text over several lines use """ or backticks. Persian files have one more form, «...», which an English file does not accept.

Joining strings

Join strings with +. When one side is a number or a bool, it is turned into text for you, so no separate conversion is needed. += adds to the end of a mut string. Salam has no "{name}" style interpolation; + is how text is put together.

str-concat.salam
func main:
    name := "Arash"
    age := 25
    message := "Hello " + name + ", you are " + age + " years old."
    println message
    println "Total: " + 2 + 3
    println "Total: " + (2 + 3)
    println "price " + 2.5 + ", paid " + true
    mut log := "started"
    log += ", connected"
    log += ", " + 3 + " files sent"
    println log
end
OutputHello Arash, you are 25 years old. Total: 23 Total: 5 price 2.5, paid true started, connected, 3 files sent

Look at the second line of output. + works from left to right, so "Total: " + 2 is already text by the time 3 arrives, and the result is Total: 23. Put the arithmetic in parentheses when you want the sum.

Bytes and characters

A string is stored as UTF-8. An English letter takes one byte, but é takes two, and so does a Persian letter. That is why there are two kinds of length and two kinds of position:

  • len(), substr and find count bytes. They are fast, and exact for ASCII text.
  • char_count(), char_substr, char_at and char_find count characters. Use them whenever the text might not be plain English.
str-chars.salam
func preview(title: str, limit: int): str:
    if title.char_count() <= limit:
        ret title
    end
    ret title.char_substr(0, limit) + "..."
end

func main:
    word := "café"
    println word.len(), word.char_count()
    greeting := "سلام"
    println greeting.len(), greeting.char_count()
    println greeting.char_at(0), greeting.char_find("م"), greeting.find("م")
    println word[0], word[0] as int
    println preview("Short title", 24)
    println preview("A long headline about the weather in Tabriz", 24)
end
Output5 4 8 4 س 3 6 c 99 Short title A long headline about th...

"سلام" is 8 bytes but 4 characters, and the letter م is character 3 but byte 6. The preview function cuts a title to a number of characters, which is what a reader expects. Cutting it with substr could split a letter in half and print a broken symbol. Indexing with word[0] also reads a single byte, as a char, so it is only the right tool for ASCII.

String methods

These methods are built into every string. None of them changes the string it is called on; they give back a new value.

str-methods.salam
func main:
    text := "  the Salam programming language  "
    clean := text.trim()
    println clean
    println "salam".len(), "salam lang".substr(0, 5)
    println "salam lang".find("lang")
    parts := "apple,pomegranate,grape".split(",")
    println parts.len(), parts.get(1)
    println "hello".concat("!")
    println "report.pdf".ends_with(".pdf"), "salam lang".starts_with("sal"), "salam lang".includes("m l")
    println "salam".upper(), "SALAM".lower(), "-".repeat(10)
    println "42".to_int() + 1, "2.5".to_float() * 2.0
end
Outputthe Salam programming language 5 salam 6 3 pomegranate hello! true true true SALAM salam ---------- 43 5
MethodWhat it does
len()The length of the string in bytes
char_count()The number of characters, which differs from the byte count outside ASCII
substr(start, count)Cut a piece out of the string, counted in bytes
char_substr(start, count), char_at(i)The same, counted in characters
find(text), char_find(text)Where some text starts (in bytes or in characters), or -1
starts_with(text), ends_with(text), includes(text)Test the start, the end, or anywhere in the string
split(separator)Break the string into a Vector<str> of pieces
trim()Remove the spaces at both ends
concat(text)Join text onto the end
upper(), lower(), repeat(n)Change the case, or repeat the text n times
to_int(), to_float()Turn text into a number

A string can never be changed in place: s[0] = 'x' is a compile error. Build a new string instead.

The str package

For more than the built-in methods, import the str package. It splits and joins, trims prefixes and suffixes, replaces, counts and changes case. The example reads a settings line of the kind you find in a config file or a form.

str-parse.salam
import str

func main:
    line := "name = Sara; city = Shiraz; lang = fa"
    fields := str.Split(line, ";")
    defer fields.free()
    each field in fields:
        pair := str.Split(field, "=")
        println str.Trim(pair.get(0)), "->", str.Trim(pair.get(1))
        pair.free()
    end
    path := "/home/sara/notes/todo.txt"
    println str.TrimPrefix(path, "/home/sara/"), str.LastIndex(path, "/")
    println str.Replace("2026-10-07", "-", "/"), str.Count("banana", "a")
    words := str.Fields("  too   many    spaces  ")
    defer words.free()
    println str.Join(words, " ")
    println str.Title("the little prince"), str.Contains(path, "notes")
end
Outputname -> Sara city -> Shiraz lang -> fa notes/todo.txt 16 2026/10/07 3 too many spaces The Little Prince true

Split and Fields return a Vector, which has to be freed, hence the defer ... free() lines. Fields splits on any run of spaces, so it is the one to use on words a person typed.

Building text in pieces

Every + makes a new string. That is fine for a line or two, but text built up in a loop is better made with a StringBuilder: str.NewBuilder starts one, str.BufAppend adds text, str.BufStr reads out the result and str.BufFree releases it. fmt.PadLeft and fmt.PadRight from the fmt package pad text to a width in characters, which lines up the columns of this receipt.

str-builder.salam
import str
import fmt

func main:
    items := ["tea", "bread", "cheese"]
    prices := [45, 30, 120]
    mut b := str.NewBuilder()
    defer str.BufFree(b)
    mut total := 0
    each i, item in items:
        str.BufAppend(b, fmt.PadRight(item, 10))
        str.BufAppend(b, fmt.PadLeft("" + prices[i], 6))
        str.BufAppend(b, "\n")
        total += prices[i]
    end
    str.BufAppend(b, str.Repeat("-", 16) + "\n")
    str.BufAppend(b, fmt.PadRight("total", 10))
    str.BufAppend(b, fmt.PadLeft("" + total, 6))
    println str.BufStr(b)
end
Outputtea 45 bread 30 cheese 120 ---------------- total 195

Comparing strings

== and != compare the contents of two strings, and < and > put them in order. str.Compare gives -1, 0 or 1, the shape a sort function wants. str.EqualFold compares without caring about upper and lower case, which is what you want for user names and email addresses.

str-compare.salam
import str

func main:
    println "apple" == "apple", "apple" < "banana", "Zebra" < "apple"
    println str.Compare("apple", "banana"), str.Compare("pear", "pear"), str.Compare("pear", "apple")
    typed := "Sara"
    stored := "SARA"
    println typed == stored, str.EqualFold(typed, stored)
    println "گل" < "لاله"
end
Outputtrue true true -1 0 1 false true false

The order is the order of the character codes, not dictionary order. Every capital letter comes before every small one, so "Zebra" < "apple" is true. Persian has the same catch: the letters پ چ ژ گ have higher codes than the rest of the alphabet, so "گل" < "لاله" is false even though گ comes before ل in Persian.

Text and numbers

to_int() and to_float() read a number from the start of a string, and they accept Persian and Arabic digits as well as English ones. They never report an error: they stop at the first character that is not part of the number, and text with no number at all gives 0. So when the text comes from a user, check it first. The example cleans up a phone number typed with Persian digits and then checks that only digits are left.

str-digits.salam
import str

func clean_phone(raw: str): str:
    ascii := str.ToEnglishDigits(raw)
    ret str.Replace(str.Replace(ascii, " ", ""), "-", "")
end

func all_digits(s: str): bool:
    if s.len() == 0:
        ret false
    end
    codes := str.CodePoints(s)
    defer codes.free()
    each c in codes:
        if not str.IsAsciiDigit(c):
            ret false
        end
    end
    ret true
end

func main:
    phone := clean_phone("۰۹۱۲ ۳۴۵-۶۷۸۹")
    println phone, phone.len(), all_digits(phone)
    println all_digits(clean_phone("0912 34x 6789"))
    println "۲۵۰۰۰".to_int() + 1, "12abc".to_int(), "abc".to_int()
    println str.ToPersianDigits("Invoice 1405, total " + 25000)
end
Output09123456789 11 true false 25001 12 0 Invoice ۱۴۰۵, total ۲۵۰۰۰

str.ToEnglishDigits and str.ToPersianDigits convert the digits inside a string and leave everything else alone. Salam always prints numbers with English digits, so ToPersianDigits is the last step when the output is for Persian readers.

Things to watch for

  • len() counts bytes. For anything a person reads (a character limit, a column width, a preview) use char_count() and the char_ methods.
  • + goes left to right: "n" + 3 + 4 is "n34", and 3 + 4 + "n" is "7n".
  • "12abc".to_int() is 12 and "abc".to_int() is 0; neither tells you something was wrong.
  • A string ends at its first zero byte: "a\0b".len() is 1. Keep binary data in a byte buffer, not in a str.
  • Ordering with < follows character codes, not the alphabet of any language.

The full list of text functions, including Unicode classification, normalization and grapheme clusters, is on the str page of the library reference.