05 — Types and strings

Previous: Control flow

日本語版: 05_types_strings.vibe.md

You have been using Int, Double, Bool, String and Char without being told much about them, and mostly that has been fine. This chapter covers the two places where it stops being fine: text is bytes, and printing a value needs a way to print it.

The exact numeric ranges are at the end of the chapter, where you can find them when a number surprises you.

String is a string of bytes

This is the one that surprises people. s[i] is an Int — the byte at that offset — not a one-character String. Indices and lengths are byte counts.

fn main with Console {
  let s = "hello"
  println("s[0] = \{s[0]}")
  println("from_byte = \{String::from_byte(s[0])}")
  println("s[:] = \{s[:]}")
  println("s[:2] = \{s[:2]}")
  println("s[2:] = \{s[2:]}")
  println("s[1:4] = \{s[1:4]}")
  println("length = \{String::length(s)}")
}
s[0] = 104
from_byte = h
s[:] = hello
s[:2] = he
s[2:] = llo
s[1:4] = ell
length = 5

String::from_byte writes a byte back out as a one-byte StringString::from_char_code is its older name. It does not encode a code point: hand it 233 ('é') and you get a lone 0xE9 byte, which is not valid UTF-8 (#2203). The four slice forms — s[:], s[:n], s[n:], s[a:b] — work the same on Bytes and on Array[T].

Because indices are bytes, slicing does not respect Unicode code point boundaries. Slicing ASCII is safe; slicing arbitrary text at an arbitrary index is not, and that is a deliberate choice — the memory is bytes, so the type says bytes rather than pretending otherwise.

Interpolation

"hello \{x}" is the only interpolation syntax, and any expression fits in the braces. A value can be interpolated when the compiler can find a to_string for its type: scalars, Option, tuples and arrays already have one, and your own types get one from derive (Show).

struct Point {
  x: Int; y: Int
} derive (Show)

fn main with Console {
  let p = Point::{
    x: 3, y: 4
  }
  println("p = \{p}")
  println("opt = \{Some(1)}")
  println("pair = \{(2, 3)}")
}
p = Point { x: 3, y: 4 }
opt = Some(1)
pair = (2, 3)

Leave derive (Show) off and interpolating a Point is a compile error, which is the intended answer — printing an address instead would be a wrong one.

Decimals

A decimal literal is a Double — 64-bit. A 32-bit Float takes an f suffix: 1.5f. Both interpolate as the decimal you wrote.

How far Int goes

Reach for this section when a number does something you did not expect; until then the short version is that Int holds whole numbers and overflow does not trap.

Int is 63 bits wide, not 64. The range is -2^62 .. 2^62-1, and a literal above 4611686018427387903 is rejected rather than truncated. Arithmetic that goes out of range wraps, as 63-bit two's complement, identically on every backend — so max + 1 is min:

fn main with Console {
  let max = 4611686018427387903
  println("max = \{max}")
  println("max + 1 = \{max + 1}")
  println("hex 0xFF = \{0xFF}")
  println("1 << 4 = \{1 << 4}")
  let neg = 0 - 8
  println("(-8) >> 1 = \{neg >> 1}")
  println("~5 = \{~5}")
  println("~(-8) = \{~neg}")
}
max = 4611686018427387903
max + 1 = -4611686018427387904
hex 0xFF = 255
1 << 4 = 16
(-8) >> 1 = -4
~5 = -6
~(-8) = 7

Three things to know coming from a C-family language:

shift, mask afterwards.

its own inverse. It binds like the other prefix operators, tighter than the infix ones.

for BigInt in @vibe/core deliberately.

Next: Mutation, regions, and escape.