【Rust】【チュートリアル】世界のトヨタが使っているというRustを使ってみたよ、その2
世界のトヨタが使っているというRustを使ってみたよ、その2
概要
最近、自動運転界隈で注目されているトヨタが開発中のCarOS(Arene)がRustという言語で開発されているということを聞きつけ、
ミーハーな自分を抑えられなかったので簡単に触ってみました。
かなりしっかりしたチュートリアルがあるので、現在はこれを勧めている段階のメモ書きになります。
今回は少しRustの特徴などについても触れていきます。
ターミナルからの入力を受け取る
このページから始めていきます。
このチュートリアルでは、いままでターミナル上に出力するだけでしたが、そうではなく、外部からの入力(数字、文字)の受け取りができるソフトを作っていきます。
ただ、このページの本質はそこではなく、Rustという言語を扱う上で重要な以下の点の説明がなされています。
- 変数の宣言方法
- immutable(後で変更できない)とmutable(後で値を変更できる)な変数の違いと宣言方法
- ライブラリのインポート(インクルード)方法
- Exception処理の重要性
まず、前回と同じくcargoを作っていきます。
Rustのworkspaceに移動し、以下のコマンドを実行します。
cargo new guessing_game
guessing_gameはプロジェクト名になるので、好きな名前で大丈夫です。
次に guessing_game/src/main.rs を好きなエディタで開いていただき、以下をコピペしてきます。
// std ライブラリ内のio(ファイル入出力)ライブラリをインポート use std::io; fn main() { // 文字列を出力するマクロ println!("Guess the number!"); println!("Please input your guess."); // Point!! // letは変数を宣言するよというマクロ // mutはこの変数をmutable(後で変更可能)な変数として宣言するよというマクロ // Rustの変数はデフォルトはimmutable(後で変更できない, Cのconstかと思ってたが、違うらしい) // let foo = 5; // immutable // let mut foo = 5; //mutable // String::new() String型(文字列型)の空のインスタンス(値を入れる箱)を作成 let mut guess = String::new(); // io::stdin() // .readline( &mut guess) //ターミナルからのinput を受け付ける関数 // 引数はmutableな変数へのreference(&) <- C++の参照渡し // Point!! // .expect("Failed to read line"); // Rustは関数からの返り値にたいして、 io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed:{}", guess); }
インポート
コメントアウト部分は自分のメモ書きになりますので、消して頂いて大丈夫です。
まず、2行目から
use std::io;
これはCでは
#include stdio.h
Pythonでいうimportに相当するものです。
ここではi/oに関わるライブラリのみが使われていますが、他にもstdライブラリだけでもかなりの数が提供されています。
なお、RustはPythonのようにインポートなしでもいくつかの機能が自動的にインポートされていますが、 Rustという言語の性質上、使わないライブラリをなるべく減らしたいため、こちらのページ の機能のみとなっています。
ちなみにこの自動的にインポートされる機能の総称としてpreludeと名付けれらています。
main文
次に4行目、
fn main(){
次に各プログラムのEntry Point(開始地点)になるmain関数です。 ここは他の言語と同じく、変数名を変えてはいけません。
Pythonでのdefと同じく、fnがRustの中で関数を宣言するマクロになります。
変数の宣言
今回のチュートリアルで恐らく一番重要なのがこの部分になります。
上のコードでの11行目。
let mut guess = String::new();
これがString型の変数の宣言になります。
まず、マクロを羅列して機能を一覧にしてみます。
let -> 変数を宣言しますよというマクロ
mut -> mutable(変数の中身を後で変えられるよう)にしますよというマクロ
String::new() -> String型のインスタンス(値を入れる箱)を作りますよというマクロ
ここで重要なのは2.のmut
つまり、Rustでは基本的に変数はimmuttableな = "後で値が変更できない” ものになります。
これは高信頼性を謳う言語ならではという感じですね。
なので、例えば以下のようなコードがあったとします。
fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); let test = guess; println!("Test={}",test); }
このコードの場合、testという変数は宣言時にguessという変数の値をコピーして格納します。
なので、実行するとguess(何も値を入れていないので空)になります。
ワーニングが出ますが、これは値を変更していないのにguessをmutableにしているためなのでここでは無視します。
ryo_udon@ryo-udon-desktop:~/workspace/rust_workspace/guessing_game$ cargo run
Compiling guessing_game v0.1.0 (/home/ryo_udon/workspace/rust_workspace/guessing_game)
warning: variable does not need to be mutable
--> src/main.rs:23:9
|
23 | let mut guess = String::new();
| ----^^^^^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
warning: 1 warnings emitted
Finished dev [unoptimized + debuginfo] target(s) in 1.14s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
Test=
しかし、これに対して以下のようにコードを変えて実行するとこうなります。
fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); let test = guess; test = "something".to_string(); //<-この行を追加 println!("Test={}",test); }
実行結果:
エラーを簡単に説明すると、最初にtestという変数を宣言したときにもう変更しないよ(immutable)と宣言したのに値を変更しているよ、と怒られています。
C言語などでは、基本的にconstを使わない限り、このようなエラーはでません。
そう考えるとこれもRustの特徴のように感じます。
ちなみにRustにもconstがあります。
constant と let(変数)との違い
三章で詳しく説明されていますが、基本的にRustではグローバル変数をletで作成できません。
なので、Constantなグローバル変数を作りたいときに
const MAX_POINTS: u32 = 100_000;
のような形で宣言するようです。
なのでconstantの一番の違いは、どこでも宣言ができること、のようです。
(違っていたらコメントをください。)
またここでは特に言及されていませんが、mutableなグローバル変数を作るのはなかなか骨が折れそうです。
こちらはまたいずれ調査して、別途まとめたいと思います。
ターミナルからの入力受付と参照渡し
40行目付近、ioライブラリを使って、ターミナルから文字列を受け取っています。
io::stdin() .read_line(&mut guess) .expect("Failed to read line");
ここでまず、.(コロン)ごとに改行していますが、これは可読性のためで、別につなげて書いても問題ありません。
次に各関数の説明をしていきます。
.read_line(&mut guess)
この関数が入力を受け付ける関数になります。
この関数が呼ばれるとターミナルで打ち込まれた文字を読み込みます。
その格納先が、&mut guess になります。
&mut guessは簡単に説明すると、guessという変数(mut)のReference(&)を渡しますよ、ということです。
Referenceというのはなにかというと、C++での参照渡しのようなものになります。
都度都度メモリを確保して値を取り込むのは処理速度的にあまりよろしくないので、Rustでは先に宣言しておいた変数のメモリに直接値を書き込みます。
例外処理
続いて、その下の**.expect()を説明していきます。
io::stdin() .read_line(&mut guess) .expect("Failed to read line");
.expect()では、.read_line()からio::Resultという型で帰ってきた実行結果に対して、もしエラーが起きていたらどういう動作をするかを宣言しています。
基本的にStandard ライブラリには中身は違えどこのio::Resultのような結果が格納できる構造体を持っています。
Rustで面白いのは、Cなどではこういった例外処理は宣言する必要がないのですが、Rustではこれを入れないとコンパイル時にワーニングが発報されます。
プログラムが突然落ちることを防ぐためにもいい仕様だなと個人的に思います。
乱数を生成する
インポート方法
ここで乱数を生成するために必要なライブラリがrandと呼ばれるライブラリになります。
そのインポート方法を説明していきます。
まず、Cargoではライブラリをバイナリの状態(コンパイルをせずに動く状態)でインポートできることが強みとしています。
なので、まずソースコードにrandを追加するまえに、今回作成したguessing_gameにrandというライブラリをこのプロジェクトに追加していきます。
まずCargo.tomlを開いて、そのdependenciesの部分にrandを追加します。
[dependencies] rand = "0.5.5"
追加後、以下のコマンドを実行するとライブラリの追加が始まります。
ちなみに cargo updateとうつとdependency内のライブラリをupdateしてくれます。
乱数生成
乱数を生成して、暗証番号を作る機能を加えたのが以下のコードになります。
// std ライブラリ内のio(ファイル入出力)ライブラリをインポート use std::io; // 乱数生成 use rand::Rng; //let mut global = String::new(); //static mut GLOBAL: String = String::new(); fn main() { // 文字列を出力するマクロ println!("Guess the number!"); println!("Please input your guess."); let secret_number = rand::thread_rng().gen_range(1,101); // Point!! // letは変数を宣言するよというマクロ // mutはこの変数をmutable(後で変更可能)な変数として宣言するよというマクロ // Rustの変数はデフォルトはimmutable(後で変更できない, Cのconstかと思ってたが、違うらしい) // let foo = 5; // immutable // let mut foo = 5; //mutable // String::new() String型(文字列型)の空のインスタンス(値を入れる箱)を作成 let mut guess = String::new(); // io::stdin() // .readline( &mut guess) //ターミナルからのinput を受け付ける関数 // 引数はmutableな変数へのreference(&) <- C++の参照渡し // Point!! // .expect("Failed to read line"); // Rustは関数からの返り値にたいして、 io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed:{}", guess); println!("Secret Number was : {}", secret_number); //let test = guess; //test = "something".to_string(); //GLOBAL = "2".to_string(); //println!("Test Global={}", GLOBAL); }
追加したのは5行目と13行目になります。
5行目はただのインポートの宣言。
13行目が実際に乱数を生成する部分になります。
let secret_number = rand::thread_rng().gen_range(1,101);
といってもここでは特に説明することはありません。
secret_numberというimmutableな変数に1~101の間でランダムに選ばれた値が代入されています。
入力した値と生成した値を比較する
次に中で生成した暗証番号と入力した数字が一致するのか、はたまた小さいのか大きいのかを判断していきます。
ちなみに値の比較のために以下のライブラリもインポートします。
use std::cmp::Ordering;
let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => { println!("You Entered:{}",guess); }, }; match guess.cmp(&secret_number) { Ordering::Less => println!("Too Small!"), Ordering::Greater => println!("Too Big!"), Ordering::Equal => println!("You Win!"), }
値の比較とその制限
上のコードで値を比較しているのが以下の部分になります。
guess.cmp(&secret_number)
しかし、ここでguessという変数とsecret_numberという変数の型が一致しないとエラーになります。
(最初に上げたコードのままだと、guessはString型、secret_numberはデフォルトのu32型になります)
そのため、ここではguessという変数をu32型で再度宣言して、trim().parse()という関数を使うことで型変換を行っています。
ちなみにその後のErr(_)は数字以外が来た場合の処理になります。
match
ここでmatchというマクロについて説明します。
例えば上のcmpという関数を使った場合、引数に与えた値に対し、
- Ordering::Less -> 小さい
- Ordering::Greater -> 大きい
- Ordering::Equal -> 一致
という結果を返します。
ここでmatchはこのcmpという関数が上の3つの結果を返してくるというのを先に確認します。
そのため、例えば下のようなコードの場合、分岐に抜けがあるよとエラーを発報してくれます。
match guess.cmp(&secret_number) { Ordering::Less => println!("Too Small!"), //Ordering::Greater => println!("Too Big!"), Ordering::Equal => println!("You Win!"), }
入力した数字と生成した数字が一致するまでループを回してみる
最終的なコードがこちらになります。
// std ライブラリ内のio(ファイル入出力)ライブラリをインポート use std::io; use std::cmp::Ordering; // 乱数生成 use rand::Rng; fn main() { // 文字列を出力するマクロ println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1,101); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); //let guess: u32 = guess.trim().parse().expect("Please type a number!"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => { println!("You Entered:{}",guess); continue; }, }; println!("You guessed:{}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too Small!"), //Ordering::Greater => println!("Too Big!"), Ordering::Equal => { println!("You Win!"); break; } } println!("Secret Number was : {}", secret_number); } }
loop
C言語でいうwhile(1)に相当するのがこちらのloopになります。
この場合、ループ内でbreakが呼ばれるか、外部からCtrl + Cによるプロセス終了のコマンドが来るまで処理が回り続けます。
実行結果
実行結果が以下になります。
生成した数字と一致したループでbreakが呼ばれ、プロセスが終了していることがわかります。
最後に
今回はチュートリアルの2章部分に少し情報を足した形でメモを残して見ました。
なかなかボリュームがあるので進みが遅いですがめげずにすすめて行きたいと思います。
ではでは。