rustlingsの回答で学ぶプログラミング言語Rust

Rust-lang,プログラミング

Rustは、エンジニアから最も愛されるプログラミング言語です。

プログラマーが集うインターネット掲示板、スタック・オーバーフロー(Stack Overflow)では、毎年世界中の開発者から投票を募っており、Rustは7年連続で最も「愛される」プログラミング言語に選ばれている。

https://www.technologyreview.jp/s/306125/how-rust-went-from-a-side-project-to-the-worlds-most-loved-programming-language/

今回は、Rustのチュートリアルの一種であるrustlingsに挑戦してみました。

回答を記すとともに、Rustが愛される理由に近づきたいと思います。
ちなみにこれは自分の回答であって、これが正解なのかは分かりません。

intro1.rs

14 |  // hint.
15 |  
16 |  // I AM NOT DONE
17 |  
18 |  fn main() {
Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.

I AM NOT DONEのコメントを消すことで次のセクションに進みます。
これは今後すべてのセクションで有効です。

intro2.rs

⚠️  Compiling of exercises/intro/intro2.rs failed! Please try again. Here's the output:
error: 1 positional argument in format string, but no arguments were given
  --> exercises/intro/intro2.rs:11:21
   |
11 |     println!("Hello {}!");
   |                     ^^

error: aborting due to previous error

ここで初めてmain関数が登場しています。
Rustでは、関数はfnから書きます。

順路通りに変数を学んでいきます。

variables1.rs

⚠️  Compiling of exercises/variables/variables1.rs failed! Please try again. Here's the output:
error[E0425]: cannot find value `x` in this scope
  --> exercises/variables/variables1.rs:11:5
   |
11 |     x = 5;
   |     ^
   |
help: you might have meant to introduce a new binding
   |
11 |     let x = 5;
   |     +++

error[E0425]: cannot find value `x` in this scope
  --> exercises/variables/variables1.rs:12:36
   |
12 |     println!("x has the value {}", x);
   |                                    ^ not found in this scope

error: aborting due to 2 previous errors

これは、Rustでは変数の宣言はletから始まることを示しています。
さらに変数は不変であるため、一度代入した変数に値の再代入をおこなうとエラーとなります。この辺はJSのletと似ていまね。

ちなみにmutを付けて宣言することで可変にできるのですが、以降の演習で出てきます。ついでに定数の宣言はconstで行いますが、constの場合はmutを付けても可変にはできません。

variables2.rs

⚠️  Compiling of exercises/variables/variables2.rs failed! Please try again. Here's the output:
error[E0282]: type annotations needed
 --> exercises/variables/variables2.rs:9:9
  |
9 |     let x;
  |         ^
  |
help: consider giving `x` an explicit type
  |
9 |     let x: /* Type */;
  |          ++++++++++++

error: aborting due to previous error

Rustは静的型付き言語なので、型の定義が必要です。
実のところ、この場合は初期値を代入するだけでコンパイラが推論してくれるのですが、バグを防ぐためにも、型定義をしておく癖を付けた方が良いと感じます。

ちなみにi8は8ビットの符号付整数です。
符号付のため負数も表すことができ、-128から127を代入することができます。

正数のみで構わない時は、u8とすることで符号なしとすることができ、0から255を代入できます。

variables3.rs

⚠️  Compiling of exercises/variables/variables3.rs failed! Please try again. Here's the output:
error[E0381]: used binding `x` isn't initialized
  --> exercises/variables/variables3.rs:10:27
   |
9  |     let x: i32;
   |         - binding declared here but left uninitialized
10 |     println!("Number {}", x);
   |                           ^ `x` used here but it isn't initialized
   |
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider assigning a value
   |
9  |     let x: i32 = 0;
   |                +++

error: aborting due to previous error

変数を参照する場合は、初期化もしくは代入をしておかないといけません。
今回は初期化の意味でゼロを代入しました。

また、String中に中括弧を入れることで変数展開されます。

variables4.rs

⚠️  Compiling of exercises/variables/variables4.rs failed! Please try again. Here's the output:
error[E0384]: cannot assign twice to immutable variable `x`
  --> exercises/variables/variables4.rs:11:5
   |
9  |     let x = 3;
   |         -
   |         |
   |         first assignment to `x`
   |         help: consider making this binding mutable: `mut x`
10 |     println!("Number {}", x);
11 |     x = 5; // don't change this line
   |     ^^^^^ cannot assign twice to immutable variable

error: aborting due to previous error

変数に対して、値の再代入をしてエラーになっています。
変数は標準では不変なため、mutを付けて宣言して可変扱いにし、エラーを回避しています。

variables5.rs

⚠️  Compiling of exercises/variables/variables5.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/variables/variables5.rs:11:14
   |
9  |     let number = "T-H-R-E-E"; // don't change this line
   |                  ----------- expected due to this value
10 |     println!("Spell a Number : {}", number);
11 |     number = 3; // don't rename this variable
   |              ^ expected `&str`, found integer

error[E0369]: cannot add `{integer}` to `&str`
  --> exercises/variables/variables5.rs:12:48
   |
12 |     println!("Number plus two is : {}", number + 2);
   |                                         ------ ^ - {integer}
   |                                         |
   |                                         &str

error: aborting due to 2 previous errors

元々は、文字列型の変数numberに対して3を再代入しようとしたため、エラーとなっていました。

この演習では、「文字列型の変数宣言の行は変更してはならない」「整数を再代入する際に変数名を変更してはならない」という制約があります。
そのため、変数名を同じくして代入する型を変える必要があり、mutは使えません。

ですので、シャドーイングというテクニックを使い、同名の変数で覆い被せます。元々は文字列型が入っていた変数を整数型が入るようにしています。

variables6.rs

⚠️  Compiling of exercises/variables/variables6.rs failed! Please try again. Here's the output:
error: missing type for `const` item
 --> exercises/variables/variables6.rs:8:13
  |
8 | const NUMBER = 3;
  |             ^ help: provide a type for the constant: `: i32`

error: aborting due to previous error

constで定数が宣言されています。
定数の場合、型宣言が必須となります。

functions1.rs

⚠️  Compiling of exercises/functions/functions1.rs failed! Please try again. Here's the output:
error[E0425]: cannot find function `call_me` in this scope
 --> exercises/functions/functions1.rs:9:5
  |
9 |     call_me();
  |     ^^^^^^^ not found in this scope

error: aborting due to previous error

main関数の中でcall_me関数を呼び出していますが、call_me関数が未定義であるためエラーとなっています。
最低限、何も処理を行わないcall_me関数を定義してやると解決します。

functions2.rs

⚠️  Compiling of exercises/functions/functions2.rs failed! Please try again. Here's the output:
error: expected type, found `)`
  --> exercises/functions/functions2.rs:12:16
   |
12 | fn call_me(num:) {
   |                ^ expected type

error[E0425]: cannot find value `num` in this scope
  --> exercises/functions/functions2.rs:13:17
   |
13 |     for i in 0..num {
   |                 ^^^ not found in this scope
   |
help: you might have meant to write `.` instead of `..`
   |
13 -     for i in 0..num {
13 +     for i in 0.num {
   |

error: aborting due to 2 previous errors

仮引数に型定義が無いためエラーとなっています。

functions3.rs

⚠️  Compiling of exercises/functions/functions3.rs failed! Please try again. Here's the output:
error[E0061]: this function takes 1 argument but 0 arguments were supplied
  --> exercises/functions/functions3.rs:9:5
   |
9  |     call_me();
   |     ^^^^^^^-- an argument of type `u32` is missing
   |
note: function defined here
  --> exercises/functions/functions3.rs:12:4
   |
12 | fn call_me(num: u32) {
   |    ^^^^^^^ --------
help: provide the argument
   |
9  |     call_me(/* u32 */);
   |            ~~~~~~~~~~~

error: aborting due to previous error

呼び出し元から実引数を渡していないため。

functions4.rs

⚠️  Compiling of exercises/functions/functions4.rs failed! Please try again. Here's the output:
error: expected type, found `{`
  --> exercises/functions/functions4.rs:18:30
   |
18 | fn sale_price(price: i32) -> {
   |                              ^ expected type

error: aborting due to previous error

関数の返り値定義がないためエラーとなっています。
RustではJSのアロー関数のような書き方をして、返り値の型を定義します。

functions5.rs

⚠️  Compiling of exercises/functions/functions5.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/functions/functions5.rs:13:24
   |
13 | fn square(num: i32) -> i32 {
   |    ------              ^^^ expected `i32`, found `()`
   |    |
   |    implicitly returns `()` as its body has no tail or `return` expression
14 |     num * num;
   |              - help: remove this semicolon to return this value

error: aborting due to previous error

末尾にセミコロンがあると、文として評価されます。
文は何らかの処理を行いますが、値が返却されません。
よってここでは、セミコロンを外して、式として評価されるように修正しています。

if1.rs

関数の返り値が数値になっているのに、何も返却していないから。
また、各テストコードがパスするように、大きい方の値を返却するif文を書いてあげます。

if2.rs

⚠️  Compiling of exercises/if/if2.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/if/if2.rs:14:9
   |
10 | pub fn foo_if_fizz(fizzish: &str) -> &str {
   |                                      ---- expected `&str` because of return type
...
14 |         1
   |         ^ expected `&str`, found integer

error: aborting due to previous error

まず、foo_if_fizzの引数と返り値にある&strとは、Rustでの参照を指します。
(今回はリテラルが引数として渡ってきているのであまり関係ありませんが)
これはどういうことかというと、Rustでは、メモリのヒープ領域に保存するようなString型変数の場合、メソッドに引数として渡したタイミングで、呼び出し元はその変数の所有権を失います。これを「ムーブされる」と呼びます。

そして、呼び出し先のメソッドのスコープが閉じるタイミングでその変数もクローズされ、メモリが解放されます。これはRustがGCを使わずにメモリ解放する仕組みなのですが、再度呼び出し元で同変数を使用することができなくなります。
これを避ける目的で使用されるのが参照というわけです。

この場合、変数`fizzish`は、呼び出し元のメモリ領域を参照しているに過ぎないため、変数の移譲は行われません。メソッドのスコープが閉じても、呼び出し先では引き続き、その変数を使用できることになるわけです。

if3.rs

⚠️  Compiling of exercises/if/if3.rs failed! Please try again. Here's the output:
error[E0308]: `if` and `else` have incompatible types
  --> exercises/if/if3.rs:15:9
   |
12 |       } else if animal == "snake" {
   |  ____________-
13 | |         3
   | |         - expected because of this
14 | |     } else {
15 | |         "Unknown"
   | |         ^^^^^^^^^ expected integer, found `&str`
16 | |     };
   | |_____- `if` and `else` have incompatible types

error: aborting due to previous error

これは変数identifierがintegerなのかfloatなのかstringなのか判断できなかったためと思われますが、それよりも初登場のロジックがあります。

それが`&’static str`です。
これは返り値のライフタイムがプログラムの全期間生きていることを指しています。すなわち、変数habitatは、どこからでも使用可能であることをコンパイラに教えていると言えます。

ただ、これもかなり微妙な気がします。
返り値は文字列リテラルであるため、スコープを閉じてもムーブが行われないはずです。わざわざこんな書き方をする必要があるのかはよく分かりません。

quiz1.rs

⚠️  Compiling of exercises/quiz1.rs failed! Please try again. Here's the output:
error[E0425]: cannot find function `calculate_price_of_apples` in this scope
  --> exercises/quiz1.rs:24:18
   |
24 |     let price1 = calculate_price_of_apples(35);
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope

error[E0425]: cannot find function `calculate_price_of_apples` in this scope
  --> exercises/quiz1.rs:25:18
   |
25 |     let price2 = calculate_price_of_apples(40);
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope

error[E0425]: cannot find function `calculate_price_of_apples` in this scope
  --> exercises/quiz1.rs:26:18
   |
26 |     let price3 = calculate_price_of_apples(41);
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope

error[E0425]: cannot find function `calculate_price_of_apples` in this scope
  --> exercises/quiz1.rs:27:18
   |
27 |     let price4 = calculate_price_of_apples(65);
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope

error: aborting due to 4 previous errors

りんごの購入数によって返却する金額が異なる`calculate_price_of_apples`関数を実装します。

primitive_types1.rs

⚠️  Compiling of exercises/primitive_types/primitive_types1.rs failed! Please try again. Here's the output:
error: expected identifier, found keyword `if`
  --> exercises/primitive_types/primitive_types1.rs:17:5
   |
17 |     if is_evening {
   |     ^^ expected identifier, found keyword

error: expected one of `:`, `;`, `=`, `@`, or `|`, found `is_evening`
  --> exercises/primitive_types/primitive_types1.rs:17:8
   |
17 |     if is_evening {
   |        ^^^^^^^^^^ expected one of `:`, `;`, `=`, `@`, or `|`

error: aborting due to 2 previous errors

正直、何がしたいのかよく分からない問題ではあった。
恐らくboolean型は真偽であるため、`if`文の制御にそのまま使えることの復習?

primitive_types2.rs

⚠️  Compiling of exercises/primitive_types/primitive_types2.rs failed! Please try again. Here's the output:
error: expected identifier, found keyword `if`
  --> exercises/primitive_types/primitive_types2.rs:25:5
   |
25 |     if your_character.is_alphabetic() {
   |     ^^ expected identifier, found keyword

error: expected one of `:`, `;`, `=`, `@`, or `|`, found `your_character`
  --> exercises/primitive_types/primitive_types2.rs:25:8
   |
25 |     if your_character.is_alphabetic() {
   |        ^^^^^^^^^^^^^^ expected one of `:`, `;`, `=`, `@`, or `|`

error: aborting due to 2 previous errors

RustのCharはユニコードのスカラー値を表すため、アルファベットも数値も絵文字を表現することもできます。
is_alphabeticメソッドは、Alphabeticプロパティを持つ場合(アルファベットなど)trueが返却、is_numericメソッドは数字であればtrueを返却します。

primitive_types3.rs

⚠️  Compiling of exercises/primitive_types/primitive_types3.rs failed! Please try again. Here's the output:
error: expected expression, found `?`
  --> exercises/primitive_types/primitive_types3.rs:11:13
   |
11 |     let a = ???
   |             ^ expected expression

error: aborting due to previous error

panic!というメソッドは処理を即座に中止し、呼び出し元に処理を返却する。
Rubyのraise(強制的に例外を発生させる)に近いものかもしれない。

これでrustlingsの20問目まで修了したことになる。
文字数が多くなってきたので、続きは別の記事にする。