Strings
Texto é bytes, e eles são UTF-8
Uma string é texto UTF-8 imutável com semântica de valor. Um char é um Unicode scalar value.
let s = "héllo";
s.len() // 6: len counts bytes
s.char_at(0) // 'h': the char whose encoding holds byte 0
s[1] // 0xC3: the byte itself, a u8, for code that walks encodings
for c in s { // one iteration per character, decoded
println(`${c}`);
}Um literal char guarda um scalar value, qualquer que seja a largura dele codificado: 'é' as i64 é 233, e c.to_string() codifica um char de volta nos bytes de onde ele veio.
Literais e escapes:
let a = "line\nand \t tab, \"quoted\", \0 and \u{1F600}";
let c = '\n';Templates: o único mecanismo de formatação
let s = `${name} has ${count} items`;Cada ${...} guarda uma expressão de um tipo primitivo ou de um tipo que implementa Display; as partes são concatenadas em uma string. Não existem placeholders {} nem format!: este é o único mecanismo do Core.
A interpolação não é um formatador: ela responde seis dígitos significativos e passa para notação de expoente por conta própria, então ${123456789.123} é 1.23457e+08. Quando o texto importa, std.fmt diz quantos dígitos e em qual base: fmt.float(v, 2) para ponto fixo, fmt.shortest(v) para o texto mais curto que é lido de volta como o mesmo double, fmt.base(v, radix) / fmt.hex, fmt.oct, fmt.bin, e fmt.group(text, 3, ",").
Operações
| Operação | Significado |
|---|---|
s + t | concatenação |
s == t, s != t | comparação de conteúdo |
s.len() | bytes |
s.char_at(i) | o char cuja codificação contém o byte i; '\0' depois do fim |
s[i] | o byte em i, um u8; depois do fim dá panic, como xs[i]. Um u8 não é um char, então s[i] == 'a' é recusado |
s.slice(a, b) | o texto do byte a ao byte b; uma posição depois do fim é o fim, uma negativa conta para trás a partir do fim, e a igual a b ou depois dele é "" |
s.starts_with(t) | teste de prefixo |
s.to_i64(), s.to_f64() | Option do número lido |
for c in s | percorre os caracteres, decodificados |
Uma posição dentro de um caractere significa o início desse caractere, em char_at e em slice: "naïve".slice(0, 3) é "na" e "naïve".char_at(3) é 'ï'. Nenhum dos dois dá panic, e um slice é sempre UTF-8.
Uma string tem reference counting feito pelo runtime: uma cópia faz retain e um release decrementa, o que o programa não consegue observar, porque strings são imutáveis. Um literal é uma constante com contagem imortal. .clone() é uma cópia profunda.
Ler, construir, e bytes que não são texto
std.str.Cursor lê uma string um caractere por vez, e as posições dele são sempre inícios de caractere: next() responde o caractere e passa por ele, peek() responde e fica, os dois None no fim; eat(ch) passa por ch quando ele é o próximo; at() é o offset, e since(m) é o texto de m até aqui. std.str.Builder monta uma string por append, com push(s), push_char(c) e to_string(): ele é a resposta a s = s + piece em um loop, que copia tudo o que já foi escrito a cada pedaço.
std.bytes guarda bytes que não são texto: um buffer é um Array<u8>, bytes.of(s) copia os bytes de uma string, e bytes.text(buf, at, len) é o único caminho de volta para uma string, um Err que nomeia o offset quando os bytes não são UTF-8. fs.read_file(p) lê um arquivo como texto verificado, e fs.read(p) como bytes.
use std.str.{Cursor, Builder};
use std.bytes;
fn words(src: string): Array<string> {
let out: Array<string> = [];
let c = Cursor.of(src);
let going = true;
while going {
while c.eat(' ') { }
let start = c.at();
let more = true;
while more {
match c.peek() {
Some(ch) => { if ch == ' ' { more = false; } else { c.next(); } }
None => { more = false; going = false; }
}
}
if c.at() > start { out.push(c.since(start)); }
}
out
}
fn main(): i32 {
let b = Builder.new();
for w in words(" one sentence in words ") { b.push(w); b.push_char('|'); }
println(b.to_string());
let raw = bytes.of("ok");
raw.push(255);
match bytes.text(raw, 0, raw.len()) {
Ok(t) => println(t),
Err(e) => println(`not text: ${e.message()}`),
}
0
}