Skip to content
Sword

Strings

A string is an immutable slice of bytes, with literals living in read-only memory:

import "std/io"
func main() !int {
greeting := "hello, world\n"
try io.Print(greeting)
// .len is bytes, not characters
if greeting.len != 13 {
return 1
}
return 0
}

Indexing a string gives you bytes. Decoding UTF-8 is a library’s job, not the type’s — std/unicode is that library:

import "std/io"
import "std/unicode"
func main() !int {
word := "ação"
try io.Printf("{} bytes, {} characters\n", word.len, unicode.Count(word))
// Walking it by character rather than by byte. Stepping one byte at a
// time lands in the middle of one, and the terminal then draws something
// that is not what is in the file.
mut at u64 = 0
mut columns u64 = 0
for at < word.len {
r := unicode.Decode(word, at) orelse break
columns += unicode.Width(r.Code)
at += r.Bytes
}
try io.Printf("{} columns\n", columns)
return 0
}

Width is what decides whether a program that draws a screen lines up: a combining mark takes no columns and a CJK character takes two.

== and != compare content, so a string behaves the way you would expect and switch over one works:

func kind(method string) int {
if method == "GET" {
return 1
}
return 0
}
func main() int {
return kind("GET") + 41
}

Joining two strings makes a third, and the third has to live somewhere. In Sword nothing allocates without being handed an allocator, so there is no + between strings at run time; there is strings.Concat, which takes one:

import "std/io"
import "std/mem"
import "std/strings"
const Greeting = "hello, " // constants join at compile time with +
const Banner = Greeting + "world"
func main() !int {
name := "ada"
// The C allocator, for a string that lives as long as you like.
mut sys := mem.NewSystem()
line := try strings.Concat(&sys, Greeting, name, "!\n")
try io.Print(line)
// Or space on the stack, gone when the function returns and never freed.
mut room := [64]u8{}
mut arena := mem.NewArena(room[..])
fields := [3]string{"a", "b", "c"}
csv := try strings.Join(&arena, fields[..], ",")
try io.Printf("{} {}\n", Banner, csv)
return 0
}

The allocator is not ceremony. It is what lets you read a signature and know a function cannot touch the heap, and it is what lets a server answer a request out of an arena that is reset, all at once, when the request is done. Printing needs none of this: io.Printf writes straight out, and fmt.Sprintf takes an allocator for the same reason Concat does.