Rust 人門
新山 祐介
権藤研2017夏ゼミ資料
https://euske.github.io/

1. はじめに

ここではRustの新奇性のみにしぼって解説する。 実際的なこと(インストール方法とか)は説明しない。

Rust = ポインタに関する諸問題を解決した C/C++

背景

2. 最初のプログラム

fn main() {
  let x = 2;  // これは
  let y = 3;  // コメント
  println!("x+y={}", x+y);
}

3. 構造体

struct Point {
  x: i32,
  y: i32,
}

fn main() {
  let p = Point { x:2, y:3 };
  println!("p={},{}", p.x, p.y);
}

4. ヒープを使う

fn main() {
  let q = Box::new(Point { x:2, y:3 });
  println!("q={},{}", (*q).x, (*q).y);
  println!("q={},{}", q.x, q.y);  // OK
}

5. C++ における RAII

main() {
  file f("hello.txt");  // ファイルが開かれる。
  string s(f.read());   // 文字列が初期化される。
  cout << s << endl;
  // f, s が自動的に解放される。
}

5. RustにおけるRAII

fn main() {
  {
    let q = Box::new(Point { x:2, y:3 });
    println!("q={},{}", q.x, q.y);
    // q は勝手に解放される。
  }
  println!("q={},{}", q.x, q.y); // ERROR
}

6. 余談: Option型

fn main()
{
  let s = Some(Point { x:2, y:3 });
  // または s = None;
  match s {
    Some(p) => println!("p={},{}", p.x, p.y),
    None    => println!("p=null"),
  }
}

Swiftの場合

7. ここまでの復習

8. C++では…

show(Point* p) {
  printf("p=%d,%d\n", p->x, p->y);
  delete p;
}

main() {
  Point* p = new Point(2, 3);
  show(p);
  delete p;  // Double free
}

9. Rustでは…

fn show(p: Box<Point>) {
  println!("p={},{}", p.x, p.y);
  // RAIIにより勝手に解放される。
}

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  show(p);  // pの所有権がshowに移る。
}

10. Rustでは…

fn show(p: Box<Point>) {
  println!("p={},{}", p.x, p.y);
}

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  show(p);
  // この時点で、pはすでにmainのものではない。
  println!("area={}", p.x * p.y); // ERROR
}

11. 所有権つづき

fn alloc() -> Box<Point> {
  let p = Box::new(Point { x:2, y:3 });
  // 所有権を返す。
  return p;
}

fn main() {
  let p = alloc();  // 所有権を得る。
  println!("p={},{}", p.x, p.y);
  // RAIIにより解放。
}

12. データの共有

fn show(p: &Box<Point>) {  // pは借り物。
  println!("p={},{}", p.x, p.y);
}

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  show(p);  // 貸しただけ。
  println!("area={}", p.x * p.y);
  // 所有者はまだ俺だから解放しなきゃ。
}

13. また貸し

fn area(p: Box<Point>) -> i32 { // 本物
  return p.x * p.y;
}
fn show(p: &Box<Point>) { // 借り物
  println!("p={},{}", p.x, p.y);
  // 借り物を本物として渡そうとすると
  println!("area={}", area(p)); // ERROR
}

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  show(p);
}

14. ちなみに…

fn main() {
  let p = Point { x:2, y :3 };
  println!("p={},{}", p.x, p.y);
  let q = p;
  println!("area={}", q.x * q.y); // OK
  println!("area={}", p.x * p.y); // ERROR
}  

15. ここまでの復習

16. C/C++ では…

class Foo {
  Point* p;
public:
  Foo(Point* p) {
    this->p = p;
  }
};

main() {
  Point* p = new Point(2,3);
  Foo* foo = new Foo(p);
  delete p;
  // foo->p は Dangling
}

図で表すと…

new new delete p foo dangling

17. Rustでは…

struct Foo {
  pt: Box<Point>,  // 所有権をもつ。
}

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  // pの所有権は foo内に移動する。
  let foo = Foo { pt:p };
  // もう p は俺の物じゃない。
  println!("p={},{}", p.x, p.y); // ERROR
  // fooが解放されると p も解放される。
}

18. どーなんの?

struct Foo {
  pt: &Box<Point>,  // 借り物
}
fn alloc(&Box<Point>) -> Foo {
  return Foo { pt:p };  // 借り物が入った本物
}

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  let foo = alloc(&p);
  println!("p={},{}", p.x, p.y); // OK...のはず
}
// 実際にはコンパイル不可

19. 生存期間という考え

struct Foo {
  pt: &Box<Point>,  // 借り物
}

図で表すと…

new new delete p Foo Fooの 生存期間

20. 生存期間識別子

struct Foo<'a> {
  // pt は、少なくとも Foo本体と期間は生きる。
  pt: &'a Box<Point>,
}

// 「与えられた p と同じ期間は生きる Foo」を返す。
fn alloc<'a>(p: &'a Box<Point>) -> Foo<'a> {
  return Foo { pt:p };
}

生存期間つきの型を使う

fn main() {
  let p = Box::new(Point { x:2, y:3 });
  let foo = alloc(&p);  // p と同じ生存期間をもつ。
  println!("p={},{}", p.x, p.y);
}

21. 生存期間識別子の省略

struct Foo<'a> {
  pt: &'a Box<Point>,
}
// 簡略版。
fn alloc(p: &Box<Point>) -> Foo {
  return Foo { pt:p };
}

22.複数の生存期間

// 「与えられた p と同じ期間は生きる Foo」を返す。
// でも q は関係ないので別の生存期間。
fn alloc<'a>(p: &'a Box<Point>, 
             q: &'b Box<Point>) -> Foo<'a> {
  println!("q={},{}", q.x, q.y);
  return Foo { pt:p };
}

23. まとめ

24. 余談: 並列化への応用

25. 余談: ほかのCライブラリとの共存