この投稿はEdward Z. Yangが2010年に書いたOCaml for Haskellers、私自身が今年頭に書いたHaskell for OCaml programmersの流れに沿っています。
目次
- プロローグ
- なぜRustを学ぶべきか
- 直接対応が可能なもの
- トレイト:Rustの型クラス
- アドホックなオブジェクトとメソッド
- 安全な参照
- 寿命と記憶域、そして管理オブジェクト
- オブジェクトの共有:RcとArc
- マクロとメタプログラミング
- リテラル
- 謝辞
- 参照
- Copyright and licensing
注: この記事の最新版は下記サイトで見られます。
http://science.raphael.poss.name/rust-for-functional-programmers.html
他のフォーマット:Source, PDF
プロローグ
C言語プログラマのためのRustと、関数型プログラマのためのRustは異なります。
他のプログラミング言語を使用した経験のあるC言語系統のプログラマなら、Rustはとても多くの新しい特徴と変わった構造を持っていると感じるでしょう。どちらかというと難解と感じるかもしれません。C++、JavaやC#プログラマでさえ、Rustは別物のように感じます。Rustはオブジェクトを持ちますがクラスを持ちません。“structed enums”を持ち、C言語系統では同等のものがない変わったマッチステートメントを使います。同じ変数を2度割り当てること避け、C系統言語では聞いたことのない変則的なルールを適用しています。
それはなぜでしょう?
現在のRustマニュアルではあえて明言していないものの、Rustは関数型言語であり、ここ数年主流となっているプログラミング言語デザインの影響を受けています。
Rust支持者が直面している問題があります。それは言語としても規律としても、関数型プログラミングが30年にわたり汚名を着せられてきたことです。例えば、関数型プログラムは判別が難しい、C言語に比べ処理が遅い、実行時には高くつく機構が必要になる(少なくともガベージコレクタ、しばしばシステムI/Oの関数型ラッパなど)、また学ぶのが難しく、変則的なシンタックスを使用するなどです。
C言語プログラマへ関数型言語としてのRustを説明する場合、受け入れてもらうにはとても骨が折れるでしょう。というのも公式のRustマニュアルとチュートリアルは明確に、下位タスクの実行に、どのようにRustを使用するか説明していますし、多くのCプログラミングパターンに相当するRustを現行のプログラミング言語に言及することなく、少しずつ説明していくとしています。
これがすべてであると言えるのですが、もしあなたがすでに関数型プログラミングについて知っていたらどうでしょうか?
Haskell、OCamlなどを使用したことのある経験者にとって、関数型プログラミングの基礎を事細かく説明したマニュアルを読むことはとても退屈なことでしょう。対象者のレベルに合わせ、以下では関数的側面から急ぎ足でRustの紹介をしていきます。
なぜRustを学ぶべきなのか
良くも悪くも、この先も大抵のハードウェアプロセッサはプログラムカウンタ、レジスタ、そしてアドレス指定のできるメモリに基づいていくだろうとされています。これは私たちが1970年代にいた場所とまったく変わっていません。当時はC言語が設計された時代であり、C言語が現在も普及している理由になっています。特にC言語の抽象機械モデルはほとんどのハードウェアのプラットフォーム上で適しているとされ、割り込みハンドラやガベージコレクタ、仮想メモリマネージャのような低レベルのシステムを構築するのにちょうどいいレベルの抽象化になっています。
しかしながらC言語の“ユーザインターフェース”、特にプリプロセッサと型システムは、とても時代遅れになっています。新しいものを学んだプログラマにとっては、率直に言ってダメな言語です。
こうしたことを背景にRustはつくられてきました。RustはC言語の抽象機械モデルを保ちますが、言語のインターフェースが刷新されています。Rustは書き方のバリエーションが豊富で、型システムはシステムコードをより安全にします。その強力なメタプログラミングファシリティはコードの自動作成の新しい方法を可能にします。
しかし、この記事を書いている時点ではRustは、いまだ安定していないことに注意してください。ほとんど予告なく、まだ変更されることがあり、公式の資料は実装と完全には同期されていません。
直接対応が可能なもの
構文 | Type | Ocaml | Haskell |
---|---|---|---|
() | () | () | () |
true | bool | true | True |
false | bool | false | False |
123 | (integer) | 123 {- :: Num a => a } | |
0x123 | (integer) | 0x123 {- :: Num a => a } | |
12.3 | (float) | 12.3 {- :: Fractional a => a } | |
'a' | char | 'a' | |
"abc" | str | "abc" | "abc" |
b'a' | u8 | 'a' | toEnum$fromEnum$'a'::Word8 |
123i | int | 123 | 123 :: Int |
123i32 | i32 | 123l | 123 :: Int32 |
123i64 | i64 | 123L | 123 :: Int64 |
123u | uint | 123 :: Word |
Haskellと同じように、Rustの数値リテラルでサフィックスがないものについでは、あらかじめ定義された型はありません。実際の型はコンテキストから推論されます。Rustの文字リテラルはどんなUnicodeのスカラ値も表すことができ、Latin-1エンコードのみのOCamlの文字とは対照的です。Rustの他のリテラルフォームは同記事内で後述するリテラルのセクションで示します。
プリミティブ型
Rust | Haskell | OCaml | ディスクリプション |
---|---|---|---|
() | () | unit | Unit型 |
bool | Bool | bool | Boolean型 |
int | Int | int | 符号付き整数型、機種依存 |
uint | Word | 符号なし整数型、機種依存 | |
i8 | Int8 | 符号付き8ビット整数、2の補数形式 | |
i16 | Int16 | 符号付き16ビット整数、2の補数形式 | |
i32 | Int32 | int32 | 符号付き32ビット整数、2の補数形式 |
i64 | Int64 | int64 | 符号付き64ビット整数、2の補数形式 |
u8 | Word8 | char | 符号なし8ビット整数 |
u16 | Word16 | 符号なし16ビット整数 | |
u32 | Word32 | 符号なし32ビット整数 | |
u64 | Word64 | 符号なし64ビット整数 | |
f32 | Float | 32ビットIEEE浮動小数点 | |
f64 | Double | float | 64ビットIEEE浮動小数点 |
char | Char | Unicodeスカラ値(サロゲートなしのコード点) | |
str | UTF-8エンコード済み文字列 |
Rustではstr
型は特別です。プリミティブなので、コンパイラは特定の文字列演算を最適化することができます。しかし、第一級ではないために、str
型の型変数を定めたり、関数に直接str
型の値を渡すことができません。プログラムでRustの文字列を使用するには、後で述べるように文字列参照を使用しなければいけません。
演算子の等価性:
== != < > <= >= && || // Rust = <> < > <= >= && || (* OCaml *) == /= < > <= >= && || {- Haskell -} + + + + - - * * / / % ! // Rust + +. @ ^ - -. * *. / /. mod not (* OCaml *) + + ++ ++ - - * * `div` / `mod` not {- Haskell -} & | ^ << >> ! // Rust land lor lxor [la]sl [la]sr lnot (* OCaml *) .&. .|. xor shiftL shiftR complement {- Haskell -}
Rustはブーリアン演算と、整数型上での単一ビットごとのNOT演算子の両方に!
を使います。単一の~
はRustでは、以下で説明するように、C言語とは違った意味を持ちます。
複合式:
Rust | OCaml | Haskell | Name |
---|---|---|---|
R{a:10, b:20} | {a=10; b=20} | R{a=10, b=20} | レコード式 |
R{a:30, ..z} | {z with a=30} | z{a=30} | アップデート関数を伴うレコード |
(x,y,z) | (x,y,z) | (x,y,z) | タプル式 |
x.f | x.f | f x | フィールド式 |
[x,y,z] | [|x;y;z|] | 配列式、固定サイズ | |
[x, ..10] | 配列式、初期値で繰り返し固定化 | ||
x[10] | x.(10) | x!10 | インデックス式(ベクター/配列) |
x[10] | x.[10] | x!!10 | インデックス式(文字列) |
{x;y;z} | begin x;y;z end | ブロック式 | |
{x;y;} | begin x;y;() end | ブロック式(unitで終わる) |
ブロック式の値は、ブロック内の最後の式の値になることに注意してください。ブロックがセミコロンで終わる場合は、その値は()
になります。
関数の型と定義:
// Rust // f : |int,int| -> int fn f (x:int, y:int) -> int { x + y }; // fact : |int| -> int fn fact (n:int) -> int { if n == 1 { 1 } else { n * fact(n-1) } }
(* OCaml *) (* val f : int * int -> int *) let f (x, y) = x + y (* val fact : int -> int *) let rec fact n = if n = 1 then 1 else n * fact (n-1)
{- Haskell -} f :: (Int, Int) -> Int f (x, y) = x + y fact :: Int -> Int fact n = if n = 1 then 1 else n * fact(n-1)
パターンマッチとガード
// Rust match e { 0 => 1, t @ 2 => t + 1, n if n < 10 => 3, _ => 4 }
(* OCaml *) match e with | 0 -> 1 | 2 as t -> t + 1 | n when n < 10 -> 3 | _ -> 4
{- Haskell -} case e of 0 -> 1 t @ 2 -> t + 1 n | n < 10 -> 3 _ -> 4
副作用を伴う再帰
// Rust fn collatz(n:uint) { let v = match n % 2 { 0 => n / 2, _ => 3 * n + 1 } println!("{}", v); if v != 1 { collatz(v) } } fn main() { collatz(25) }
(* OCaml *) let rec collatz n = let v = match n % 2 with | 0 -> n / 2 | _ -> 3 * n + 1 in Printf.printf "%d\n" v; if v <> 1 then collatz v let _ = collatz 25
{- Haskell -} collatz n = do let v = case n `mod` 2 of 0 -> n `div` 2 _ -> 3 * n + 1 putStrLn $ show v when (v /= 1) $ collatz v main = collatz 25
明らかに、OCamlと同様に、Rustは正格(先行)評価を行い、関数が副作用のある式を含むことが可能です。
Rustが用いるLLVMコードジェネレータは上記の関数に対して末尾呼び出しの除去ができますが、Rustは末尾呼び出しの除去を(現在のところ)保証していないことに注意してください。不確かな場合は、下記が同等です。
let mut n = 25u; while n != 1 { n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 } println("{}", n); }
レコード型の式とフィールドアクセス
// Rust struct Point { x : int, y : int } let v = Point {x:1, y:2}; let s = v.x + v.y
(* OCaml *) type Point = { x : int; y : int } let v = { x = 1; y = 2 }; let s = v.x + v.y
{- Haskell -} data Point = Point { x :: Int, y :: Int } v = Point {x = 1, y = 2} s = (x v) + (y v)
自由な型パラメータ(ジェネリックなデータと関数型)
// Rust type Pair<a,b> = (a,b) // id<t> : |t| -> t fn id<t>(x : t) -> t { x }
(* OCaml *) type ('a, 'b) pair = 'a * 'b (* val id : 't -> 't *) let id x = x
{- Haskell -} type Pair a b = (a, b) id :: t -> t id x = x
代数的データ型
// Rust enum Option<T> { None, Some(T) } // x : Option<t> match x { None => false, Some(_) => true }
(* OCaml *) type 't option = None | Some of 't (* x : t option *) match x with | None -> false | Some _ -> true
{- Haskell -} data Maybe a = Nothing | Just a {- x : Maybe t -} case x of Nothing -> False Just _ -> True
ラムダ式と高階関数
// Rust // ||int,int| -> int, int| -> int fn ff(f:|int,int|->int, x:int) -> int { f (x, x) } // m2 : |int| -> int fn m2(n : int) -> int { ff ((|x,y| { x + y }), n) }
(* OCaml *) (* (int*int->b)*int -> int *) let ff (f, x) = f (x, x) (* m2 : int -> int *) let m2 n = ff ((fun(x,y) -> x + y), n)
{- Haskell -} ff :: ((int,int)->int, int) -> int ff (f, x) = f (x, x) m2 :: Int -> Int m2 n = ff ((\(x,y) -> x + y), n)
トレイト:Rustの型クラス
Rustの“トレイト”はHaskellの型クラスに似ています。
Haskellと主に違う点は、トレイトはドット表記法の式に対してのみ介在するということです。すなわちa.foo(b)
のような形です。
しかしながら、C++、Java、C#、OCamlのプログラマは、トレイトを伝統的なオブジェクトクラスと混同してはいけません。トレイトはまさに型クラスです。つまり、トレイトを、プリミティブ型を含む任意のデータ型に追加することができます。
例
// Rust trait Testable { fn test(&self) -> bool } impl Testable for int { fn test(&self) -> int { if *self == 0 { false } else { true } } } fn hello(x:int) -> bool { x.test() }
{- Haskell -} class Testable a where test :: a -> Bool instance Testable Int where test n = if n == 0 then False else True hello :: Int -> Bool hello x = test x
トレイトのメソッド宣言において、識別子“self”
は、そのメソッドが適用される実際のオブジェクトを示します。
Haskellと同様に、Rustのトレイトは演算子のオーバーロードに使うことができます。例えば、Peano整数に対して新たな直和型を定義する場合は、
// Rust enum Peano { Zero, Succ(Box<Peano>) }
{- Haskell -} data Peano = Zero | Succ Peano
そして、PartialEq
クラスを例示することで、比較演算子==
をPeano整数の間にオーバーロードできます。
// Rust impl PartialEq for Peano { fn eq(&self, other:&Peano) -> bool { match (self, other) { (&Zero, &Zero) => true, (&Succ(ref a), &Succ(ref b)) => (a == b), (_, _) => false } } }
{- Haskell -} instance Eq Peano where (==) self other = case (self, other) of | (Zero, Zero) -> True | (Succ a, Succ b) -> (a == b) | (_, _) -> False
また、Haskellと同様に、トレイトはメソッドに対するデフォルト実装を提供できます。インスタンスが特殊化を省略する時に使用されます。
// Rust trait PartialEq { fn eq(&self, other:&Self) -> bool; fn ne(&self, other:&Self) -> bool { !self.eq(other) } }
{- Haskell -} class Eq a where (==) : a -> a -> Bool (!=) : a -> a -> Bool (!=) x y = not (x == y)
トレイト宣言内のメソッド宣言において、識別子“self”
はそのトレイトが適用される実際の型を指します。
Rustにおいてオーバーロード可能な各演算子には、標準ライブラリの中に対応するトレイトがあります。
式 | 拡張 | トレイト | 同等のHaskellクラス/メソッド |
---|---|---|---|
a == b | a.eq(b) | std::cmp::PartialEq | class PartialEq a where (==) : a -> a -> bool |
a != b | a.ne(b) | std::cmp::PartialEq | class PartialEq a where (/=) : a -> a -> bool |
a < b | a.lt(b) | std::cmp::PartialOrd | class PartialOrd a where (<) : a -> a -> bool |
a > b | a.gt(b) | std::cmp::PartialOrd | class PartialOrd a where (>) : a -> a -> bool |
a <= b | a.le(b) | std::cmp::PartialOrd | class PartialOrd a : Eq a where (<=) : a -> a -> bool |
a >= b | a.ge(b) | std::cmp::PartialOrd | class PartialOrd a : Eq a where (>=) : a -> a -> bool |
a + b | a.add(b) | std::ops::Add<b,c> | class Add a b c where (+) : a -> b -> c |
a - b | a.sub(b) | std::ops::Sub<b,c> | class Sub a b c where (-) : a -> b -> c |
a * b | a.mul(b) | std::ops::Mul<b,c> | class Mul a b c where (*) : a -> b -> c |
a / b | a.div(b) | std::ops::Div<b,c> | class Div a b c where (/) : a -> b -> c |
a % b | a.rem(b) | std::ops::Rem<b,c> | class Rem a b c where (%) : a -> b -> c |
-a | a.neg() | std::ops::Neg<c> | class Neg a c where (-) : a -> c |
!a | a.not() | std::ops::Not<c> | class Not a c where (!) : a -> c |
*a | a.deref() | std::ops::Deref<c> | class Deref a c where (*) : a -> c |
a & b | a.bitand(b) | std::ops::BitAnd<b,c> | class BitAnd a b c where (&) : a -> b -> c |
a | b | a.bitor(b) | std::ops::BitOr<b,c> | class BitOr a b c where (|) : a -> b -> c |
a ^ b | a.bitxor(b) | std::ops::BitXor<b,c> | class BitXor a b c where (^) : a -> b -> c |
a << b | a.shl(b) | std::ops::Shl<b,c> | class Shl a b c where (<<) : a -> b -> c |
a >> b | a.shr(b) | std::ops::Shr<b,c> | class Shr a b c where (>>) : a -> b -> c |
forループは次のように、特殊なトレイトstd::iter::Iterator
を使用します。
// Rust // the following expression: [<label>] for <pat> in <iterator expression> { body...; } // ... expands to (internally): match &mut <iterator expression> { _v => [label] loop { match _v.next() { None => break, Some(<pat>) => { body... } } } }
メソッドnext
はIterator
が実装します。next
の戻り値の型はOption
です。これは、“繰り返すものが残っていない”ことを意味する値None
、あるいは次の繰り返し値がx
であることを意味する値Some(x)
を持つことができます。
アドホックなオブジェクトとメソッド
トレイトが提供する仕組みに加えて、どのstruct
やenum
も、“impl
”を使って1つ以上のメソッドインターフェースを使ってデコレートすることができます。デコレートは定義から分離され,異なるモジュール内ででデコレートすることができます。
// Rust struct R {x:int}; // in some module: impl R { fn hello(&self) { println!("hello {}", self.x); } } // possibly somewhere else: impl R { fn world(&self) { println!("world"); } } // Example use: fn main() { let v = R {x:10}; v.hello(); v.world(); (R{x:20}).hello(); }
// Rust enum E {A, B}; // in some module: impl E { fn hello(&self) { match self { &A => println!("hello A"), &B => println!("hello B") } } } // possibly somewhere else: impl E { fn world(&self) { println!("world"); } } // Example use: fn main() { let v = A; v.hello(); v.world(); B.hello(); }
安全な参照
C言語と同様に、Rustの関数パラメータはデフォルトで、値によって渡されます。大きなデータ型では、データのコピーはコストが高くなるかもしれないため、代わりに参照を使うことも考えられます。
型T
のどんなオブジェクトv
でも、式“&v
”を使ってそのオブジェクトを指す参照を作ることができます。その参照自体はその時、型&T
を持つことになります。
use std::num::sqrt; struct Pt { x:f32, y:f32 } // This works, but may be expensive: fn dist1(p1 : Pt, p2: Pt) -> f32 { let xd = p1.x - p2.x; let yd = p1.y - p2.y; sqrt(xd * xd + yd * yd) } // Same, using references: fn dist2(p1 : &Pt, p2: &Pt) -> f32 { let xd = p1.x - p2.x; let yd = p1.y - p2.y; sqrt(xd * xd + yd * yd) } // Usage: fn main() { let a = Pt { x:1.0, y:2.0 }; let b = Pt { x:0.0, y:3.0 }; println!("{}", dist1(a, b)); println!("{}", dist2(&a, &b)); }
この例で示しているように、Rustはstruct
参照のためのシンタックスシュガーを提供しています。つまりp1
の型が&Pt
でありPt
がx
という名前のフィールドを持つ場合は、p1.x
と書くことも可能です。このシンタックスシュガーはメソッド呼び出し(x.foo()
)にも利用できます。
しかし、他の多くのケースでは、参照される値が*
単行演算子で“取得される”か、パターンが&
でマッチしなければなりません。
fn add3(x : &int) -> int { println!("x : {}", *x); // OK 3 + x; // invalid! (+ cannot apply to &int) 3 + *x; // OK } fn test<t>(x : &Option<t>) -> bool { match *x { None => false, Some(_) => true } // Also valid, equivalent: match x { &None => false, &Some(_) => true } }
単純な参照では下部のオブジェクトの変更を許していません。このため、参照によって変更できることが望ましい場合は、次のように“&mut
”を使用します。
fn incx(x : &int) { x = x + 1; // invalid! (mismatched types in "int& = int") *x = *x + 1; // invalid! (*x is immutable) } fn inc(x : &mut int) { x = x + 1; // invalid! (x is immutable) *x = *x + 1; // OK } fn main() { inc(&3); // invalid! (3 is immutable) inc(&mut 3); // OK, temp var forgotten after call let v = 3; inc(&v); // invalid! (v is immutable) inc(&mut v); // OK, temp var forgotten after call let mut w = 3; inc(&w); // OK }
Rustの型システムは、参照を通した可変な別名を許しません。つまり、C言語とは違って、参照で別名を使って同じオブジェクトを変更することはできません。これは借用という概念を通して行われます。つまり、オブジェクトの所有権が参照によって借用されている間は、元の変数を使用できないということです。例を挙げます。
fn main() { let mut a = 1; a = 2; // OK println!("{}", a); // OK, prints 2 // {...} introduces a new scope: { let ra = &mut a; a = 3; // invalid! (a is borrowed by ra) *ra = 3; // OK println!("{}", a); // invalid! (a is borrowed) println!("{}", *ra); // OK, prints 3 }; // end of scope, ra is dropped. println!("{}", a); // OK, a not borrowed any more (prints 3) }
“すべての値はデフォルトでは不変”であることや所有権・借用のルールと共に、参照型はRustの型システムの核となる特徴です。この特徴によって、Rustの型システムはC言語よりも根本的に安全なものとなっています。
寿命と記憶域、そして管理オブジェクト
注
本セクションで示す概念とシンタックスは、Rustのバージョン0.11で追加されたものです。Rust 0.10以前のバージョンは異なる概念、用語、シンタックスを使用しています。
また、この記事の執筆時点(2014年7月)において、公式のRustマニュアルとチュートリアルは最新の言語実装のものにまだ更新されていません。本セクションはマニュアルではなく実装に即しています。
はじめに
RustはC言語と同じ抽象機械モデルについて定義されています。この抽象機械にはメモリのセグメントがあり、言語のランタイム機構はプログラムの実行中にメモリのセグメントの割り当てと解除を行うことができます。
抽象機械において、RustではC言語と同様に下記の2つの概念が定義されています。
- オブジェクトの記憶域によって、そのオブジェクトが格納されるメモリの型が決まります。
- オブジェクトの寿命つまり記憶期間は、オブジェクトが割り当てられた点から解除された点までの時間のセグメントです。
C言語におけるメモリ関連の問題はすべて、Cプログラムでは寿命外(すなわち割り当て前または解除後)あるいは記憶域外(すなわちメモリの下位アドレスまたは上位アドレス)のオブジェクトを指す参照を操作できることから起こっています。
Rustは、オブジェクトが寿命外または記憶域外で使用できないことを確実にすることで、そうした問題を徹底的に防ぐようにしています。
Rustの記憶域とC言語の記憶域
C言語やRustの抽象機械には、4種類の記憶域、つまりstatic(静的)、thread(スレッド)、automatic(自動)、allocated(割り当て)があります。
記憶域 | 使用メモリの型 | C言語での例 |
---|---|---|
static | プロセス全体の特殊セグメント | static int i; |
automatic | スタック | int i; (in function scope) |
allocated | グローバルヒープ | int *i = malloc(sizeof(int)); |
thread | スレッドごとのヒープ | _Thread_local int i; |
C言語では、オブジェクトの寿命はその記憶域だけで決まります。
記憶域 | C言語での寿命 |
---|---|
static | プログラムの開始から終了まで |
automatic | スコープの始まりから終わりまで、1駆動フレームにつき1 |
allocated | mallocからfreeまで(またはmmapからmunmapまで、など) |
thread | オブジェクト割り当てからスレッド終了まで |
Rustでは、staticとautomaticのオブジェクトの寿命はC言語と同じです。しかしながら、
- Rustは新たに“Box”型を導入しています。ヒープに割り当てられたオブジェクトのための専用の構文をもち、そのオブジェクトは管理オブジェクトと呼ばれます。
Rustは、さまざまな型付けルールに関連して、ボックスに対する多くの管理戦略をサポートしています。
-
デフォルトのボックス管理戦略は、ボックスの所有者が1人だけであることを保証します。そうすればコンパイラは、いつボックスの寿命が終わるかを正確に把握でき、参照カウントやガベージコレクションなどのようにシステムに余計な負荷をかけることなく、割り当てを安全に解除することができます。
-
もう1つの戦略は、遅延ガベージコレクションを使うGCです。Rustのランタイムシステムが不要だと決めた時点で、GCボックス用の記憶域が再生され再利用が可能になります(再生には想定外の時間がかかるかもしれません)。GCボックスへの参照は1つだけ(ユニーク)である必要はありません。GCボックスは、グラフのような共通のノードを持った複雑なデータ構造を構築する上で適切な型です。
-
C言語とは違い、Rustはスレッド間での管理オブジェクトの共有を許しません。Erlangと同様に、オブジェクトは割り当て毎に1人の所有者を持ちます。そしてほとんどのオブジェクトは、それぞれのスレッドだけで使われます。これによって、大抵の場合はデータの競合を防ぐことができます。また、1つのスレッドに所有権を限定することにより、GCオブジェクトのガベージコレクションをスレッド間で同期する必要がありません。つまり実装が簡単で、動作を速くできます。
-
通常のオブジェクトへの参照と同様に、管理オブジェクトを参照している可変な別名をRustは許しません。
-
ベクターへのアクセスはすべてチェックされ、参照はポインタの演算をサポートしません。
-
Rustは管理されていないポインタを使用しないよう強く推奨しています。安全ではない特性があるとして、システムでコンパイラに警告を誘発させます。
ボックスの作成と使用
Rustの管理オブジェクトの扱い方は、比較的シンプルです。“puts objects into boxes”の式にあるbox
キーワードによって、オブジェクトの寿命が動的に管理されます。ボックスの不可変性と所有権については、前述した参照と同様の考え方です。
fn main() { let b = box 3i; // b has type Box<int> b = 4i; // invalid! can't assign int to box<int> *b = 4i; // invalid! b is immutable, so is the box let mut c = box 3i; // c has type mut Box<int> *c = 4i; // OK let v = box vec!(1i,2,3); // r1 has type Box<Vec<int>> *v.get_mut(0) = 42; // invalid! v is immutable, so is the box let mut w = box vec!(1i,2,3); *w.get_mut(0) = 42; // OK, rust 0.11.1+ also permits w[0] = 42 let z = w; // z has type Box<Vec<int>>, captures box println!("{}", w); // invalid! box has moved to z w = box vec!(2i,4,5); // overwrites reference, not box contents println!("{} {}", w, z); // OK, prints [2,4,5] [42,2,3] }
box
キーワードは、実際にはbox(HEAP)
の省略形です。“box(A) E
”は、A
によって割り当てられたメモリオブジェクトにE
の値の結果を置くことを意味し、これがトレイトです。Rustのバージョン0.11において、これ以外に標準ライブラリ内にあるアロケータは、ガベージコレクションされたオブジェクトであるGC
だけです。
式 | 型 | 意味 |
---|---|---|
box v | Box<T> | “v”のコピーへのユニークな参照。”box(HEAP) v”の省略形 |
box(GC) v | Gc<T> | “v”のコピーへのガベージコレクションされたスマートポインタ |
box(A) v | ??<T> | スマートポインタの一種。”A”は特殊なトレイトを実装する |
再帰データ構造
管理オブジェクトは、適切な再帰的代数的データ型を実装するための”失われた環”になっています.
// Rust enum Lst<t> { Nil, Cons(t, Box<Lst<t>>) } fn len<t>(l : &Lst<t>) -> uint { match *l { Nil => 0, Cons(_, ref x) => 1 + len(*x) } } fn main() { let l = box Cons('a', box Cons('b', box Nil)); println!("{}", len(l)); }
(* OCaml *) type 't lst = Nil | Cons of 't * 't lst let len l = match l with | Nil -> 0 | Cons(_, x) -> 1 + len x let l = Cons('a', Cons('b', Nil)); print_int (len l)
{- Haskell -} data Lst t = Nil | Cons t (Lst t) let len l = case l of Nil -> 0 Cons _ x -> 1 + len x main = do let l = Cons 'a' (Cons 'b' Nil) putStrLn $ show l
オブジェクトの共有:RcとArc
ボックスが提供する利便性を補完するものとして、Rustの標準ライブラリには、参照がカウントされる不変オブジェクトへのラッパーが2つ実装されています。これにより複数のオーナーからの参照が可能になります。
use std::rc::Rc; fn main() { let b = Rc::new(3i); let c = &b; println("{} {}", b, c); // OK }
参照カウントを使えば、最後の参照が終了したその時に、オブジェクトの割り当てが解除されたことが保証されます。ボックスの使用のトレードオフは、新しい参照が作られたり参照が終わったりするたびに、参照カウントを更新しなければならない、ということです。
std::rc::Rc
とstd::arc::Arc
という2つの実装が提供されています。どちらも同じインターフェースを提供します。なぜ同じようなものがあるかというと、プログラマにパフォーマンス上のトレードオフを与えるためです。Rcはメモリを使いません。だから軽量で、つまり速いのです。しかしスレッド間で共有できません。Arcはメモリを使います。Rcと比べるとやや効率が悪いですが、スレッド間でデータを共有できます。
マクロとメタプログラミング
マクロを定義する基本的なシンタックスは次のようになります。
macro_rules! MACRONAME ( (PATTERN...) => (EXPANSION...); (PATTERN...) => (EXPANSION...); ... )
例えば、次に示すマクロは、for
ループをPascalのように定義したものです。
macro_rules! pfor ( ($x:ident = $s:expr to $e:expr $body:expr) => (match $e { e => { let mut $x = $s; loop { $body; $x += 1; if $x > e { break; } } }}); ); // Example use: fn main() { pfor!(i = 0 to 10 { println!("{}", i); }); }
このマクロが、ローカルネームを付けるに当たってどのようにマッチステートメントを使っているかに注目してください。これによって1度しか値を返さないようになっています。
Scheme同様、マクロも再帰可能です。例えば次に示すマクロでは、step
があってもなくてもpfor
を実装するために再帰を使っています。
macro_rules! pfor ( ($x:ident = $s:expr to $e:expr step $st:expr $body:expr) => (match $e,$st { e, st => { let mut $x = $s; loop { $body; $x += st; if $x > e { break; } } }}); ($x:ident = $s:expr to $e:expr $body:expr) => (pfor!($x = $s to $e step 1 $body)); ); // Example use: fn main() { pfor!(i = 0 to 10 step 2 { println!("{}", i); }); }
マクロは可変個引数であり、シンタックス内の任意の繰り返しは、マクロの1つの引数で記録できます。例えば、次に示すマクロでは、各引数においてprintln!
を呼び出し、それは任意の型になり得ます。
macro_rules! printall ( ( $( $arg:expr ),* ) => ( $( println!("{}", $arg) );* ); ); // example use: fn main() { printall!("hello", 42, 3.14); }
シンタックスは次のようになります。マクロ式の左側(pattern)では、$( PAT )DELIM*
が、DELIM
によって区切られているPAT
のゼロまたはそれ以上の出現に一致し、右側(expansion)では、$( TEXT )DELIM*
が、DELIM
によって区切られたTEXT
の1回またはそれ以上の繰り返しを展開します。展開される繰り返しの回数は、含まれるマクロの引数がマッチする回数によって決まります。示した例の中では、それぞれの引数(カンマによって区切られる)は、セミコロンによって区切られるprintln!
という呼び出しで代用されています。
リテラル
Rustにはさまざまな数値リテラルの語彙があります。
Rustのシンタックス | 同義 | 型 | Haskellでのシンタックス | OCamlでのシンタックス |
---|---|---|---|---|
123i | int | 123 :: Int | 123 | |
123u | uint | 123 :: Word | ||
123i8 | i8 | 123 :: Int8 | ||
123u8 | u8 | 123 :: Word8 | Char.chr 123 | |
123i16 | i16 | 123 :: Int16 | ||
123u16 | u16 | 123 :: Word16 | ||
123i32 | i32 | 123 :: Int32 | 123l | |
123u32 | u32 | 123 :: Word32 | ||
123i64 | i64 | 123 :: Int64 | 123L | |
123u64 | u64 | 123 :: Word64 | ||
1_2_3_4 | 1234 | (integer) | ||
1234_i | 1234i | int | ||
0x1234 | 4660 | (integer) | 0x1234 | 0x1234 |
0x1234u16 | 4660u16 | u16 | 0x1234 :: Word16 | |
0b1010 | 10 | (integer) | 0b1010 | |
0o1234 | 668 | (integer) | 0o1234 | 0o1234 |
b'a' | 97u8 | u8 | 'a' | |
b"a" | [97u8] | [u8] | ||
12.34 | (float) | 12.34 | ||
12.34f32 | f32 | 12.34 :: Float | ||
12.34f64 | f64 | 12.34 :: Double | 12.34 | |
12e34 | 1.2e35 | (float) | 12e34 | |
12E34 | 1.2e35 | (float) | 12E34 | |
12E+34 | 1.2e35 | (float) | 12E+34 | |
12E-34 | 1.2e-33 | (float) | 12E-34 | |
1_2e34 | 12e34 | (float) | ||
1_2e3_4 | 12e34 | (float) |
文字、バイト、文字列によるエスケープのリテラル
シンタックス | 同義 | シンタックス | 同義 | シンタックス | 同義 |
---|---|---|---|---|---|
'\x61' | 'a' | b'\x61' | 97u8 | "\x61" | "a" |
'\\' | '\x5c' | b'\\' | 92u8 | "\\" | "\x5c" |
'\'' | '\x27' | b'\'' | 39u8 | "\"" | "\x22" |
'\0' | '\x00' | b'\0' | 0u8 | "\0" | "\x00" |
'\t' | '\x09' | b'\t' | 9u8 | "\t" | "\x09" |
'\n' | '\x0a' | b'\n' | 10u8 | "\n" | "\x0a" |
'\r' | '\x0d' | b'\r' | 13u8 | "\r" | "\x0d" |
'\u0123' | "\u0123" | ||||
'\U00012345' | "\U00012345" |
C言語ファミリでよく使われるエスケープ(\a、\fなど)や、8進法表記のエスケープ(例、\0123)は、Rustでは使えないことに注意してください。
最後に、RustはPython同様に、raw文字列、複数の文字列のデリミタをサポートしており、これにより文字列内のデリミタのオカレンスを引用することを避けています。
シンタックス | 文字列の値 | シンタックス | 値 |
---|---|---|---|
"foo" | foo | b"foo" | [102u8,111,111] |
"fo\"o" | fo"o | b"fo\"o" | [102u8,111,34,111] |
r"fo\n" | fo\n | rb"fo\n" | [102u8,111,92,110] |
r#"fo\"o"# | fo\"o | rb#"fo\"o"# | [102u8,111,92,34,111] |
"foo#\"#bar" | foo#"#bar | b"foo#\"#bar" | [102u8,111,111,35,34,35,98,97,114] |
r##"foo#"#bar"## | foo#"#bar | rb##"foo#"#bar"## | [102u8,111,111,35,34,35,98,97,114] |
謝辞
RedditやHacker Newsでクオリティの高いコメントを寄せてくれた多くの人たちに感謝します。この記事の初版に磨きをかけるに当たって、いただいたコメントが大変参考になりました。
参照
- The Rust Language Tutorial, version 0.10, April 2014.
- The Rust Language Tutorial, version 0.11.0, July 2014.
- The Rust Guide, version 0.11.0, July 2014.
- Aaron Turon, Rust Guidelines, 2014.
- Will Yager. Why Go is not good, 2014. Explains how Go lacks many features found in Rust and thereby fails to be a modern functional language.
- Edward Z. Yang, OCaml for Haskellers, October 2010.
- Raphael ‘kena’ Poss, Haskell for OCaml programmers, March 2014.
- Xavier Leroy et al., The OCaml system release 4.01, September 2013.
Copyright © 2014, Raphael ‘kena’ Poss. Permission is granted to distribute, reuse and modify this document according to the terms of the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.