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] — は Bytes と Array[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 + 1 は 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
C 系の言語から来たときに知っておきたい点が3つあります:
>>は算術シフトで符号拡張します。>>>はありません。論理シフトが
欲しければ後からマスクしてください。
~は63ビット値に対する補数なので~x == -x - 1で、それ自身が逆演算に
なります。他の前置演算子と同じく、中置演算子より強く結合します。
- 暗黙の昇格はありません。63ビットを超える必要が出たら、
@vibe/coreの
BigInt を意識して使います。