Rust

概要

RustはC言語、C++の置換を目指したProgramming Language

  • 性能
  • メモリ安全性
  • 安全な並行性

を特徴とする。

Zigと方向性が同じ。

Memo

book-summaryのエラー

  • https://github.com/kd-collective/book-summary/blob/db03f5c7e95ee8ebad5d446e7ed1ddc2a0d49561/src/main.rs#L222
  • book-summaryを実行したときにエラーになる
  • 配列のインデックスがないといっている
  • tomlが含まれているところで実行するとだめっぽい
  • 該当箇所的にもそう
  • 設定にtitleがないとエラーになる
    • だが、titleがなくてもbuildはできる
    • メインツールでオプショナルなのに依存するのはおかしいし、不親切なのでせめてメッセージを出すようにしたい

ビルドエラー failed to run custom build command for `proc-macro2 v1.0.42`

新しいマシンにしたとき発生した。原因不明。ライブラリ名は若干変わるときがある。共通しているのはcustom build commandということ。手元のビルドだけでなく、cargo installも失敗する。

さっぱりわからない。検索しても似たような現象の人は出ない。開発用のライブラリを入れたら直ったという記事もあったが、すでに入っている。

  • バージョンを下げてもだめだった
  • curlで入れる方法をやり直してもだめだった
  • aptで入れ直してもだめだった

cloneとcopyの違い

まず明確にしておかなけばならないのは、Copyトレイトを実装した型で行われる値のコピーと、Cloneトレイトを実装した型で行われる値のclone()は多くの場合は異なる操作だということです。

前者はメモリ上のバイト列を単純にコピーする操作で、Cのmemcpyに相当します。このような操作は一般的にはshallow copy(浅いコピー)と呼ばれます。

後者はその型に実装されたclone()メソッドを呼び出す操作です。どういうことをするかはclone()メソッドの実装に依存しますが、多くの場合はmemcpyよりも複雑なdeep copy(深いコピー)を行います。

たとえばString型は文字列データをVec<u8>型で持ちます。Vec<T>型はT型の各要素を格納するメモリ領域と、3つの要素を持つ構造体で表現されています。その構造体の要素は、前述のメモリ領域を指すポインタ、Vec<T>の容量(capacity)、Vec<T>の長さになっています。

仮にStringがCopyトレイトを実装できたとすると、shallow copyによってコピーされるのはVec<u8>の構造体の部分だけになります。u8型の各要素を格納するメモリ領域はコピーされず、また構造体の中にあるポインタも(memcpy相当なので)みな同じアドレスを指します。つまり、コピーによって作られた複数のStringが、同じu8型の要素を共有することになります。

Copyトレイトはmemcpy相当の操作だけで完全にコピーできる型にしか実装できないようになっています。たとえばu8型の値はmemcpyでコピーすれば十分(他にコピーするものがない)なのでCopyトレイトが実装されています。一方、Vec<T>はmemcpyでは不十分(shallow copyになる)なのでCopyトレイトを実装できません。

一方、Vec<T>のclone()メソッドはdeep copyを行うように実装されています。各要素を格納するメモリ領域が新たに割り当てられ、構造体の値も新たに作られます。また個々の要素も、そのclone()メソッドを呼ぶことでコピーされます。つまり、clone()によって作られた複数のStringは、それぞれが異なるメモリ領域にあるu8型の要素を持つことになります。

さて本題のRustの配列型の初期化構文[s; 2]では、なぜsのところにCopyトレイトを実装した型しか受け付けないかですが、その理由はドキュメントに書かれてないので、あくまでも私の推測を元に説明します。

Rustの配列はあらかじめ言語に組み込まれている型(プリミティブ型)です。また、初期化の構文[s; 2]も言語に組み込まれています。そのような構文からは、clone()のようにRustコードで実装されたメソッドを呼び出すことができないのかもしれません。メソッドは呼べないので、型の実装に依存しないmemcpy相当のコピーを行うのではないでしょうか。memcpy相当の操作で完全にコピーできることを保証するには、sがCopyトレイトを実装していなければなりません。

Vec<T>には配列の初期化にそっくりのvec![s; 2]の構文があります。Vec<T>はプリミティブ型ではなく、Rustコードで実装されたユーザー定義型です。またvec![s; 2]はマクロで実装されており、これもコンパイル時にRustコードに変換されます。そのためだと思いますが、vec![s; 2]ではsがCopyを実装している必要はなく、Cloneを実装していれば初期化できます。

マクロの例

マクロの例。identはidentityの略。

macro_rules! declare_rustdoc_lint { ($(#[$attr:meta])* $name: ident, $level: ident, $descr: literal $(,)?) => { declare_tool_lint! { $(#[$attr])* pub rustdoc::$name, $level, $descr } } }

declare_rustdoc_lint! { / The `broken_intra_doc_links` lint detects failures in resolving / intra-doc link targets. This is a `rustdoc` only lint, see the / documentation in the [rustdoc book]. / / [rustdoc book]: ../../../rustdoc/lints.html#broken_intra_doc_links BROKEN_INTRA_DOC_LINKS, Warn, “failures in resolving intra-doc link targets” }

printlnマクロの定義。

macro_rules! println { () => { \(crate::print!("\n") }; (\)($arg:tt)*) => {{ $crate::io::_print(\(crate::format_args_nl!(\)($arg)*)); }}; }

ライフタイムの概要

Rustにおいて参照はすべてライフタイムを保持する。ライフタイムは、その参照が有効になるスコープ。ライフタイムも暗黙的に推論される。複数の型の可能性があるときには、型を注釈しなければならない。

ライフタイムの主な目的は、ダングリング参照…参照するつもりだったデータ以外のデータを参照してしまうこと…を回避すること。コンパイラは借用チェッカーによってチェックしてエラーを出す。

fn main() {
  {
    let r;

    {
      let x = 5;
      r = &x;
    }

    println!("r: {}", r);
  }
}
error[E0597]: `x` does not live long enough
  --> /tmp/babel-Mwh0df/rust-H3aWMg:8:11
   |
8  |       r = &x;
   |           ^^ borrowed value does not live long enough
9  |     }
   |     - `x` dropped here while still borrowed
10 |
11 |     println!("r: {}", r);
   |                       - borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

関数のジェネリックなライフタイム。

// 引数は参照である。longest関数に引数の所有権を奪ってほしくないから
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}
error[E0106]: missing lifetime specifier
 --> /tmp/babel-Mwh0df/rust-eT95tY:2:33
  |
2 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
2 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.

↑戻り値の型はジェネリックなライフタイム引数である引数であるといっている。返している参照が xy のどちらを参照しているか、コンパイラにはわからないから。ifブロックは x への参照を返し、elseブロックは y への参照を返すので、どちらかわからない。

エラーを修正するためには、借用チェッカーが解析できるように、参照間の関係を定義するジェネリックなライフタイム引数を追加する。ライフタイム注釈は、参照の生存期間を変えることはない。ライフタイム注釈は、ライフタイムに影響することなく、複数の参照のライフタイムのお互いの関係を記述する。

ライフタイム引数の名前はアポストロフィーで始まらなければならず、通常全て小文字で、ジェネリック型のように短い。慣例的に 'a という名前を使う。

&i32 // ただの参照
&'a i32 // 明示的なライフタイム付きの参照
&'a mut i32 // 明示的なライフタイム付きの可変参照

ライフタイム注釈をつける。

  • 何らかのライフタイム’aに対して、関数は2つの引数を取り、どちらも少なくともライフタイム’aと同じだけ生きる文字列スライスであるとコンパイラに教えるようになった
  • 返る文字列スライスもライフタイム’aと同じだけ生きると、コンパイラに教えている。実際にはlongest関数が返す参照のライフタイムは、渡された参照のうち、小さいほうのライフタイムと同じということになる
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}
main();

The longest string is abcd ()

  • ライフタイム引数を指定するとき、いかなる値のライフタイムも変更していない。longest関数は、 xy の正確な生存期間を知っている必要はなく、このシグニチャを満たすようなスコープを’aに代入できることを知っているだけ
  • 関数にライフタイムを注釈するときは、注釈は関数の本体ではなくシグニチャに付与する
    • コンパイラは注釈がなくとも関数内のコードを解析できる。が、関数に関数外からの参照や関数外への参照がある場合、コンパイラが引数や戻り値のライフタイムも自力で解決することはほとんど不可能になる。
    • そのライフタイムは関数が呼び出されるたびに異なる可能性があるので、手動でライフタイムを注釈する必要がある

トレイトの概要

トレイト:共通の振る舞いを定義する - The Rust Programming Language 日本語版

トレイトを使用すると、あるジェネリックが、特定の振る舞いをもつあらゆる型になり得ることを指定できる。

pub trait Summary {
    fn summarize(&self) -> String;
}

トレイトを型に実装する。

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

// impl トレイト for 構造体
impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Big news!"),
        location: String::from("Tokyo"),
        author: String::from("Me"),
        content: String::from("Birthday"),
    };

    println!("1 new news: {}", article.summarize());

    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    };

    println!("1 new tweet: {}", tweet.summarize());
}
main()

1 new news: Big news!, by Me (Tokyo) 1 new tweet: horse_ebooks: of course, as you probably already know, people ()

制約: 外部のトレイトを外部の型に対して実装できない。コヒーレンス、孤児のルールと呼ばれる特性の一部。この制約によって、他の人のコードが自分のコードを壊したり、その逆が起きないことを保証する。

デフォルト実装。各メソッドのデフォルト実装があると、すべての型に対して実装を要求しないので便利。

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle { }

fn main() {
    let article = NewsArticle {
        headline: String::from("Big news!"),
        location: String::from("Tokyo"),
        author: String::from("Me"),
        content: String::from("Birthday"),
    };
    println!("New article available! {}", article.summarize());
}
main()

New article available! (Read more…) ()

デフォルト実装は、自らのトレイトのデフォルト実装を持たない他のメソッドを呼び出すことができる。↑の場合は実装メソッドがないため、デフォルト実装が使われた。

一部だけデフォルト実装にする例。

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("Read more from {}...", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    };

    println!("summarize: {}", tweet.summarize());
    println!("summarize_author: {}", tweet.summarize_author());
}

main()

summarize: Read more from @horse_ebooks… summarize_author: @horse_ebooks ()

引数itemのsummarizeメソッドを呼ぶ関数notifyを定義する。引数itemはSummaryトレイトを実装している何らかの型。

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("Read more from {}...", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

// 引数: &impl トレイト
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// ↑と等価で、冗長に書いたバージョン。トレイト境界
// 山カッコの中にジェネリックな型引数の宣言を書き、型引数の後ろにコロンを挟んでトレイト境界を置く
// pub fn notify<T: Summary>(item: &T) {
//   // 速報! {}
//   println!("Breaking news! {}", item.summarize());
// }

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    };

    notify(&tweet);
}

main()

Breaking news! Read more from @horse_ebooks… ()

トレイトを実装している型を返す。impl Trait構文を戻り値型のところで使うことで、あるトレイトを実装する何らかの型を返す。

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("Read more from {}...", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

// impl Trait構文を戻り値型のところで使うことで、**あるトレイトを実装する**何らかの型を返す
// 具体的な型を指定してないところがポイント
// これはクロージャとイテレータを扱うときに特に便利。ある関数はIteratorトレイトを実装するある型を返すのだ、と簡潔に指定できる
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}

fn main() {
  let tweet = returns_summarizable();
  println!("result: {}", tweet.summarize_author());
}

main()

result: @horse_ebooks ()

  • ただしimpl Traitの制約として、1種類の型を返す場合にのみ使える

関数に渡したスライスの値の型が、PartialOrdとCopyを実装する限りコンパイルできる、ジェネリックなlargest関数。

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

main()

The largest number is 100 The largest char is y ()

トレイト境界を使用して、メソッド実装を条件分けする。

  • Pair<T>は常にnew関数を実装する。
  • Pair<T>は、内部の型Tが比較を可能にするPartialOrdトレイトと出力を可能にするDisplayトレイトを実装しているときのみ、cmp_displayメソッドを実装する。
  use std::fmt::Display;

  struct Pair<T> {
      x: T,
      y: T,
  }

  impl<T> Pair<T>{
      fn new(x: T, y: T) -> Self {
          Self { x, y }
      }
  }

  impl <T: Display + PartialOrd> Pair<T> {
      fn cmp_display(&self) {
          if self.x >= self.y {
              println!("The largest member is x = {}", self.x);
          } else {
              println!("The largest member is y = {}", self.y);
          }
      }
  }

  fn main() {
      let pair = Pair{ x: 1, y: 2};
      pair.cmp_display();
  }
main();

The largest member is y = 2 ()

別のトレイトを実装するあらゆる型に対するトレイト実装を条件分けできる。トレイト境界を満たすあらゆる型にトレイトを実装することは、ブランケット実装と呼ばれ、Rustの標準ライブラリで広く使用される。

impl<T: fmt::Display + ?Sized> ToString for T { / A common guideline is to not inline generic functions. However, / removing `#[inline]` from this method causes non-negligible regressions. / See https://github.com/rust-lang/rust/pull/74852, the last attempt / to try to remove it. #[inline] default fn to_string(&self) -> String { let mut buf = String::new(); let mut formatter = core::fmt::Formatter::new(&mut buf); // Bypass format_args!() to avoid write_str with zero-length strs fmt::Display::fmt(self, &mut formatter) .expect(“a Display implementation returned an error unexpectedly”); buf } }

整数はDisplayを実装するので、整数値を対応するString値に変換できる。

fn main() {
    println!("{}", 3.to_string());
}
main();

3 ()

ジェネリクスの概要

ジェネリック型、トレイト、ライフタイム - The Rust Programming Language 日本語版

enum Option<T> {
  Some(T),
  None,
}

型Tの値を保持するSomeと、値を何も保持しないNone。

複数のジェネリックな型を使用できる。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

メソッド定義にも使える。

  struct Point<T> {
    x: T,
    y: T,
  }

impl<T> Point<T> {
  fn x(&self) -> &T {
    &self.x
  }
}

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

matchとOption

match制御フロー演算子 - The Rust Programming Language 日本語版

enum Coin {
  Penny,
  Nickel,
  Dime,
  Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
  match Coin {
    Coin::Penny => 1,
    Coin::Nickel => 5,
    Coin::Dime => 10,
    Coin::Quarter => 25,
  }
}

値に束縛されるパターン。Quarterが保持するenumを増やす。

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            // stateに束縛されるのは、UsState::Alabama
            println!("state quarter from {:?}!", state);
            25
        },
    }
}

value_in_cents(Coin::Quarter(UsState::Alabama))

state quarter from Alabama! 25

Option<T>とのマッチ。

  • マッチは包括的なので、もしNoneアームがなかったとしたらエラーを出してくれる。
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

単にif letで短く書ける。

if let Some(thing) = thing {
   // 中身の値があるとき実行
  }else {
   // 中身の値がないとき実行
  }
error[E0433]: failed to resolve: use of undeclared type `Coin`
 --> /tmp/babel-Mwh0df/rust-jR2DGi:4:12
  |
4 |     if let Coin::Quarter(state) = coin {
  |            ^^^^ use of undeclared type `Coin`

error[E0425]: cannot find value `coin` in this scope
 --> /tmp/babel-Mwh0df/rust-jR2DGi:4:35
  |
4 |     if let Coin::Quarter(state) = coin {
  |                                   ^^^^ not found in this scope

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0425, E0433.
For more information about an error, try `rustc --explain E0425`.

enumと構造体

Enumを定義する - The Rust Programming Language 日本語版

enum IpAddr {
    V4(String),
    V6(String),
}

fn main () {
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));
}

main()

別の例。

enum Message {
  Quit,
  Move { x: i32, y: i32 },
  Write(String),
  ChangeColor(i32, i32, i32),
}

Optionも、標準ライブラリにより定義されているEnum。初期化処理(prelude)に含まれているため、明示的にスコープに導入する必要がない。

pub enum Option<T> { / No value #[lang = “None”] #[stable(feature = “rust1”, since = “1.0.0”)] None, / Some value `T` #[lang = “Some”] #[stable(feature = “rust1”, since = “1.0.0”)] Some(#[stable(feature = “rust1”, since = “1.0.0”)] T), }

Option値を使って数値型や文字列型を保持する例。

let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;

Option<T>とTは異なる。Option<T>を使うためには変換が必要になる。nullである場合を明示的に処理する必要がある。

構造体のインスタンス化

メソッド記法 - The Rust Programming Language 日本語版

newはない。

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!("The area of the rectangle is {} square pixels.", area(&rect1));
}

fn area(rectangle: &Rectangle) -> u32{ rectangle.width *
                                       rectangle.height }

main()

The area of the rectangle is 1500 square pixels. ()

構造体で情報出力するために、debug注釈を追加する。

#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

fn main() {
  let rect = Rectangle { width: 1, height: 1};
  println!("rect is {:?}", rect);
}

main()

rect is Rectangle { width: 1, height: 1 } ()

構造体上にメソッドを実装する。

#[derive(Debug)]
struct Rectangle {
    width: i32,
    height: i32,
}

impl Rectangle {
    // メソッドなので、selfはRectangle。
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle{ width: 1, height: 1 }
    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

新しいメソッド。

#[derive(Debug)]
struct Rectangle {
    width: i32,
    height: i32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle{ width: 30, height: 50 };
    let rect2 = Rectangle{ width: 10, height: 40 };
    let rect3 = Rectangle{ width: 60, height: 45 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

main()

Can rect1 hold rect2? true Can rect1 hold rect3? false ()

ライフタイム

ライフタイムを使うと、構造体に他の何かに所有されたデータへの参照を保持させることができる。

フィールドのない構造体: ユニット様構造体

構造体を定義し、インスタンス化する - The Rust Programming Language 日本語版

また、一切フィールドのない構造体を定義することもできます!これらは、()、ユニット型と似たような振る舞いをすることから、 ユニット様構造体と呼ばれます。ユニット様構造体は、ある型にトレイトを実装するけれども、 型自体に保持させるデータは一切ない場面に有効になります。トレイトについては第10章で議論します。

pub struct Monster {}

マクロ作成

コンパイル前に動的にコードを展開して、実行することで柔軟性を得られる。

fn impl_component(ast: &DeriveInput) -> proc_macro2::TokenStream { let name = &ast.ident;

quote! { impl #impl_generics Component for #name #ty_generics #where_clause { type Storage = #storage<Self>; } }

unwrap()は何か

unwrap() は、 Option<T> 型や Result<T, E> 型の値(つまり、何かしらの値を ラップ している値)から中身の値を取り出す関数。たとえば Option<T> 型の値に対して unwrap() を呼ぶと、それが内包する T 型の値を返す。それらの型には値が入ってない可能性もあり、入ってない場合にはunwrapは失敗する。

rust - Rustの“unwrap()”は何をするものですか? - スタック・オーバーフロー

イテレータを定義する

反復子を使用する - Learn | Microsoft Docs

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

#[derive(Debug)]
struct Counter {
    length: usize,
    count: usize,
}

impl Counter {
    fn new(length: usize) -> Counter {
        Counter {
            count: 0,
            length,
        }
    }
}

impl Iterator for Counter {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        if self.count <= self.length {
            Some(self.count)
        } else {
            None
        }
    }
}

fn main() {
    for number in Counter::new(10) {
        println!("{}", number);
    }
}

Box、スマートポインタは何か

スマートポインタはポインタのように振る舞うだけでなく、追加のメタデータと能力があるデータ構造。 スマートポインタ - The Rust Programming Language 日本語版

Rustでは、boxを使う。

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

Rustでスマートポインタを利用するのに使う。 スタックではなくヒープにデータを保存する。

使う場面。

  • コンパイル時にはサイズを知ることができない型があり、正確なサイズを要求する文脈でその型の値を使用するとき
  • 多くのデータがあり、その所有権を移したいが、その際にデータがコピーされないようにしたいとき
  • 値を所有する必要があり、特定の型であることではなく、特定のトレイトを実装する型であることのみ気にかけているとき

ヒープのデータを指すBox<T>を使用する - The Rust Programming Language 日本語版

変更を検知して自動ビルドする

変更したら自動でcargo runしてほしいときがある。

cargo install cargo-watch
cargo watch -x run

println! マクロとは何か

println!("hello world!");
println!("{} days", 31);
println!("{0}, this is {1}, {1}, this is {0}", "Alice", "Bob");
println!("{} of {:b} people know binary, the other half doesn't", 1, 2);
println!("{number:>0width$}", number=1, width=6);
println!("{subject} {verb} {object}", object="the lazy dog", subject="the quick brown fox", verb="jumps over");

hello world! 31 days Alice, this is Bob, Bob, this is Alice 1 of 10 people know binary, the other half doesn’t 000001 the quick brown fox jumps over the lazy dog

引数チェックもしてくれる。

println!("My name is {0}, {1} {0}", "Bond");
error: invalid reference to positional argument 1 (there is 1 argument)
 --> /tmp/babel-wnDbpn/rust-W98kSP:2:27
  |
2 | println!("My name is {0}, {1} {0}", "Bond");
  |                           ^^^
  |
  = note: positional arguments are zero-based

error: aborting due to previous error

マーカーの変更。

println!("This struct `{}` won't print...", Structure(3));
error[E0425]: cannot find function, tuple struct or tuple variant `Structure` in this scope
 --> /tmp/babel-wnDbpn/rust-If17CF:2:45
  |
2 | println!("This struct `{}` won't print...", Structure(3));
  |                                             ^^^^^^^^^ not found in this scope

error: aborting due to previous error

For more information about this error, try `rustc --explain E0425`.
#[derive(Debug)]
struct Structure(i32);
println!("This struct `{:?}` won't print...", Structure(3));

This struct `Structure(3)` won’t print…

パッケージ

ホームディレクトリのチルダを展開するライブラリ

どういうわけかデフォルトで展開してくれないので、ライブラリで変換する必要がある。

extern crate shellexpand;

fn main() {
    let cwd = format!("{}", shellexpand::tilde("~/"));
    Command::new("ls").current_dir(&cwd);
}

OSディレクトリライブラリ

OS間のディレクトリの違いを吸収するライブラリ。ミニマルでコードを読みやすい。

EmacsをRustで書き直すプロジェクト

EmacsのC言語で書かれた部分をRustに書き直すリポジトリがある。 remacs/remacs: Rust Emacs

超高速検索できるripgrep

高速検索するripgrepは、ほかのどのgrepツールより早いらしい。

repository
BurntSushi/ripgrep

外観がかっこいいShell, nushell

System Craftersの動画Integrating Nushell with Emacsのコラボ回で出たパッケージ。 リッチな出力形式、便利コマンドがすごい。

repository
nushell/nushell: A new type of shell

Tasks

TODO Getting Started - Rust Compiler Development Guide

Rustコンパイラ開発のガイド。

Writing an OS in Rust

OSをrustで書く。

型やライブラリの調べ方   DontKnow

何かしたいときにうまく型を見つけるためにはどうしたら良いのか。ドキュメントを見てもいまいちわからない。

型でorはどうやるのか   DontKnow

文字列もしくは整数、みたいな型はどうやって表現するのか。TypeScriptでいうところのunion型みたいな。

Introduction - The Specs Book

ECSとSpecsのドキュメント。

Rust の最初のステップ - Learn | Microsoft Docs

Microsoftのチュートリアル。

Why is building a UI in Rust so hard? | Warp

RustでのUI作成はなぜつらいか。

Reference

Archives

DONE Getting started - Command Line Applications in Rust

コマンドラインプログラムを作るチュートリアル。

DONE 手を動かして考えればよくわかる 高効率言語 Rust 書きかた・作りかた   Read

構造体に実装する

    fn main() {
      let body = Body::new(163.0, 75.2, "田中");
      body.print_result();
      let body = Body::new(158.2, 55.0, "鈴木");
      body.print_result();
      let body = Body::new(174.2, 54.2, "井上");
      body.print_result();
    }

  struct BmiRange {
      min: f64,
      max: f64,
      label: String,
  }

  impl BmiRange {
    fn new(min: f64, max: f64, label: &str) -> Self {
      BmiRange{ min, max, label: label.to_string() }
    }

    fn test(&self, v: f64) -> bool {
      (self.min <= v) && (v < self.max)
    }
  }

  struct Body {
    height: f64,
    weight: f64,
    name: String,
  }

  impl Body {
    fn new(height: f64, weight: f64, name: &str) -> Self {
        Body{ height, weight, name: name.to_string() }
    }

    fn calc_bmi(&self) -> f64 {
      self.weight / (self.height / 100.0).powf(2.0)
    }

    fn print_result(&self) {
      let bmi = self.calc_bmi();
      let bmi_list = [
        BmiRange::new(0.0, 18.5, "低体重"),
        BmiRange::new(18.5, 25.0, "普通体重"),
        BmiRange::new(25.0, 30.0, "肥満1度"),
        BmiRange::new(30.0, 35.0, "肥満2度"),
        BmiRange::new(35.0, 40.0, "肥満3度"),
      ];
      let mut result = String::from("不明");
      for range in bmi_list {
        if range.test(bmi) {
          result = range.label.clone();
          break;
        }
      }
      println!("{}さん、 BMI={:.1}, 判定={}",
      self.name, bmi, result);
    }
  }
main()

田中さん、 BMI=28.3, 判定=肥満1度 鈴木さん、 BMI=22.0, 判定=普通体重 井上さん、 BMI=17.9, 判定=低体重 ()

None, Result

struct Counter {
  value: i64,
}

impl Counter {
  fn new() -> Self {
    Counter { value: 0 }
  }

  fn inc(&mut self) {
    self.value += 1;
    println!("value={}", self.value);
  }
}

fn count(counter: Option<&mut Counter>) {
  match counter{
    None => return,
    Some(c) => c.inc(),
  };
}

fn main() {
  let mut a = Counter::new();
  count(Some(&mut a));
  count(Some(&mut a));
  let a = None;
  count(a);
}
main();

value=1 value=2 ()

DONE Introduction - Roguelike Tutorial - In Rust

roguelikeを作る長大なチュートリアル。 とりあえず14章までやり、理解を確かめるため自作改造フェーズに入った。 残りの部分はチュートリアルとしてやるというより、自作するうえで都度参照していく。

DONE clone ツール

git cloneをコード管理するツール。初期化したときに、再度cloneしまくるのがメンドいため。とりあえず完了。