05 — 型と文字列

前: 制御フロー

English version: 05_types_strings.vibe.md

ここまで Int / Double / Bool / String / Char を、詳しい説明なしに 使ってきました。たいていはそれで足りています。この章が扱うのは、それで 足りなくなる2箇所です — テキストはバイトであること、そして値を印字するには 印字の方法が要ること。

正確な数値の範囲は章の末尾にあります。数値が想定外の振る舞いをしたときに そこを見てください。

String はバイト列

ここが一番驚かれるところです。s[i]Int — その位置のバイト — であって 1文字の String ではありません。添字も長さもバイト数です。

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 はバイトを 1 バイトの String として書き戻します — String::from_char_code は同じ関数の古い名前です。コードポイントをエンコード しません: 233 ('é') を渡すと孤立した 0xE9 バイトが返り、これは 妥当な UTF-8 ではありません (#2203)。4つのスライス形式 — s[:] / s[:n] / s[n:] / s[a:b] — は BytesArray[T] でも 同じように使えます。

添字がバイトなので、スライスは Unicode のコードポイント境界を尊重しません。 ASCII のスライスは安全ですが、任意のテキストを任意の位置で切るのは安全では ありません。これは意図的な選択です — メモリがバイトである以上、型もバイトだと 言う方が、そうでないふりをするより誠実だからです。

補間

"hello \{x}" が唯一の補間構文で、波括弧には任意の式が入ります。補間できる のは、その型の to_string をコンパイラが見つけられる場合です。スカラー、 Option、タプル、配列は最初から持っており、自分の型は 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)

derive (Show) を外すと Point の補間はコンパイルエラーになります。それが 意図した答えです — 代わりにアドレスが印字される方が、誤った答えです。

小数

小数リテラルは Double(64ビット)です。32ビットの Float には f 接尾辞が要ります: 1.5f。どちらも書いたとおりの小数として補間されます。

Int はどこまで入るか

この節は、数値が想定外の振る舞いをしたときに読んでください。それまでは 「Int は整数を保持し、オーバーフローで落ちることはない」で足ります。

Int の幅は63ビットで、64ではありません。範囲は -2^62 .. 2^62-1 で、 4611686018427387903 を超えるリテラルは切り捨てではなく拒否されます。 範囲を出た演算は63ビットの2の補数としてラップします。どのバックエンド でも同一なので、max + 1min です:

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

C 系の言語から来たときに知っておきたい点が3つあります:

欲しければ後からマスクしてください。

なります。他の前置演算子と同じく、中置演算子より強く結合します。

BigInt を意識して使います。

次: ミューテーション・region・エスケープ