冬休みの間、何かプログラミングの勉強をしたくなったので、前から気になっていたRustを勉強してみることにした。勉強しながら感じたことをつらつらと書いてみました。特にまとまっていません。

以下のURLを参照しながら勉強しています。 https://doc.rust-lang.org/book/ch01-02-hello-world.html

まずは Hello, World!

fn main() {
    println!("Hello, world!");
}

試しにエンドコロン削除 特に問題ないし、

println!が気になったので説明を読むとこれはマクロのコールだと、 ためしに!を削除してみる、 エラーになった。

error[E0423]: expected function, found macro `println`
 --> main.rs:2:5
  |
2 |     println("Hello World!");
  |     ^^^^^^^ help: use `!` to invoke the macro: `println!`

error: aborting due to previous error

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

コンパイラからの情報見るに関数期待していてマクロがきたと言っているので、printlnはマクロとして先に定義しているのか、それとも記法が違うのか。Ch19で説明とあるから一旦従う

Hello, Cargo!

先程は rustc でコンパイルしたが、rust は cargo というマネジメントツールを使うみたい。以下のようにやることでプロジェクトなんかも作れちゃう。

cargo new hello_world
cd hello_world
$ tree -a
.
├── Cargo.toml
├── .git
│   ├── config
│   ├── description
│   ├── HEAD
│   ├── info
│   ├── objects
│   └── refs
├── .gitignore
└── src
    └── main.rs

git情報が既に入っているのが、いいですね。 src ディレクトリの中に Hello, World! が既に入っているのもいいですね。

Programming a Guessing Game

Guessing Game を作りながらプログラミングを学ぶ。

let foo = 5; // immutable
let mut bar = 5; // mutable
io::stdin().read_line(&mut guess)
    .expect("Failed to read line");

usingを使うことでパッケージを短縮できるが、必須ではない。このあたりはJavaチック。

&mut guess

For now, all you need to know is that like variables, references are immutable by default. Hence, you need to write &mut guess rather than &guess to make it mutable

&の参照型はデフォルトでは変更不可。したがって&mut guess と書くことで変更可能にすることが出来る。 &guess だと変更不可。

次のお題は

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }

を追加することで入力と生成された文字との比較をして推測ゲームを完成させる。 上が比較なのはわかるし、Ordering::Lessとかの後の=>はラムダなのかなでも、カールブラケットの中に入れているが不思議。

match は多言語での switch

でっ結果はコンパイルエラー、型不一致。 入力値はStringなので、という話 じゃ入力値を数字に変換しましょう

let guess: u32 = guess.trim().parse()
    .expect("Please type a number!");

なんか不思議な書き方。

We create a variable named guess. But wait, doesn’t the program already have a variable named guess? It does, but Rust allows us to shadow the previous value of guess with a new one. This feature is often used in situations in which you want to convert a value from one type to another type. Shadowing lets us reuse the guess variable name rather than forcing us to create two unique variables, such as guess_str and guess for example. (Chapter 3 covers shadowing in more detail.)

でとりあえず、変数を新しく用意する必要はないと、このへんはjavascript的な書き方できるけど、きっちり裏では型もってそうなイメージ。

let guess: u32 とりあえずここがわからん

  • u32 : unsigned 32 bit number
  • i32 : 32 bit number

guess: u32 って何かなと思ったら。

let guess: u32 = guess.parse().unwrap();let guess = guess.parse::<u32>(); は等価だそうだ。後者のほうが私はわかりやすいけど、慣れたら前者のほうが早くかけるし、式の頭で何が行われるかわかりやすいのかな。

結局 : i32 はただのrustの型宣言のやり方。

// --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

やはり => はラムダ式的なものか、でもLessとかの値がどこからきたのかわからない。 <-fn cmp の戻り値からきてる。

// --snip--

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

// --snip--

型変換のところで間違えた入力があった場合の変更で、 そうかmatchとセットになっていることがわかるので、 parse 関数の戻り値を match {}; で受けてるようだ。

Common Programming Concepts

Variables and Mutability

基本はimmutableつまりconstがデフォルトなのかと思ったら。constもあった。 letはあくまでmut を使えるよと言うだけみたい。

fn main() {
const MAX_POINTS: u32 = 100_000;
}

100_000 地味に便利。100,000と同じ感覚

Shadowing

変数の使い回しが出来るってことだけど、注意点はletがいるってことか。 再宣言でオーバーライド出来るってことね。

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;

    println!("The value of x is: {}", x);
}
fn main() {
    let mut x = 5;
    x = x + 1;
    x = x * 2;

    println!("The value of x is: {}", x);
}

上2つは同じ結果になるけど、前者はShadowingを使っている、以下は変数値を書き換えている。 メモリ的には前者かな おそらく Shadowing した瞬間に前の変数はドロップされるので、メモリ的には変わらない。CPU的にはわからない。ベンチマークそのうちしてみる。

あと、

fn main() {
let spaces = "   ";
let spaces = spaces.len();
}

はOKだけど、

fn main() {
let mut spaces = "   ";
spaces = spaces.len();
}

はだめ、Shadwingは型の再定義はできない。

次に、

fn main() {
    // This binding lives in the main function
    let long_lived_binding = 1;

    // This is a block, and has a smaller scope than the main function
    {
        // This binding only exists in this block
        let short_lived_binding = 2;

        println!("inner short: {}", short_lived_binding);

        // This binding *shadows* the outer one
        let long_lived_binding = 5_f32;

        println!("inner long: {}", long_lived_binding);
    }
    // End of the block

    // Error! `short_lived_binding` doesn't exist in this scope
    println!("outer short: {}", short_lived_binding);
    // FIXME ^ Comment out this line

    println!("outer long: {}", long_lived_binding);
    
    // This binding also *shadows* the previous binding
    let long_lived_binding = 'a';
    
    println!("outer long: {}", long_lived_binding);
}

上の例のアウトプットは

inner short: 2
inner long: 5
outer long: 1
outer long: a

となる、スコープ内で定義したShadowはスコープ外では定義されていないので反映されない。

Data Types

多分何回か見に来ることになるからリンクを貼っておく。

https://doc.rust-lang.org/book/ch03-02-data-types.html

気になったのは

  • Integer Type に arch があること。pythonみたいにどっちになるかもって考えなくても良いし、環境毎に最大値とかとりやすくていい
  • Binaryの書き方が 0b1111_0000
  • Compound Types に Tupleがある

Functions

関数の戻り値の型宣言が面白い

fn five() -> i32 {
    5
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

return 値はセミコロンなし。 セミコロンありでも無しでも動くと思っていたら。

fn plus_one(x: i32) -> i32 {
    x + 1
}

は問題なく動くいて、もし5が入ってきたら、6を返すけど、

fn plus_one(x: i32) -> i32 {
    x + 1;
}

は空のタプルを返す。

Control Flow

きちんと for-eachある

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}

Wrap up

前から触りたかったけど、触る機会が無かったRustを思い立って触っただけ、 今のところは少し癖があるけど結構好きかもしれない。