下準備
Rustのインストール
https://www.rust-lang.org/ja/learn/get-started
VSCodeのインストール
https://azure.microsoft.com/ja-jp/products/visual-studio-code/
VSCodeにRustAnalyzerプラグインを導入する。
そもそもRustってどんな言語?
-バックエンド開発に向いている
-C言語を超える高速な処理
-ヒューマンエラーを許さない信頼性の高い言語構造
-言語構造レベルでスレッドセーフが保障されているのでマルチスレッド処理が得意
プロジェクトを作る
コマンドプロントで
cd プロジェクトを作りたい場所のパス
cargo new new_project
これでプロジェクトフォルダが生成されるので出来たらそれをVSCodeで開く。
エクスプローラーでフォルダを右クリックしてCodeで開くを選択する、もしくはVSCode内から開く。
動かしてみる
src/main.rsをダブルクリックして開く。そうすると既にHello Worldが書かれているので、実行する。RustAnalyzerの準備が終わるとRunボタンが左上に表示されるので押す。
そうすると下のターミナルにHelloWorldが出力される。
test機能
これをコピペして追加する。
#[test]
fn test_func(){
println!("I am true main function.");
}
そうするとRun Testボタンが現れるのでそれをクリックするとtest_funcが実行される。
変数定義
超強力な型推論が備わっていて、殆どの場合自分で型を書く必要が無い。
let i = 10;
i = 5;//エラー! 変数は標準で変更不能(イミュータブル)
let f = 10.0;//右辺によって型が決定される
let j:f64 = 10.0;//型を指定して定義する
let i = "aiueo";//以前使った変数を再定義できる(シャドーイング)
let mut x = 10;//mutを付けると、変更可能な変数になる
x = 50;
f64は64bit浮動小数点型。要するに他の言語でのdouble。
一般的な名称 | Rust |
---|---|
int | i32 |
long | i64 |
unsigned_int | u32 |
char | u8 |
float | f32 |
標準出力
let x = 10.0;
println!("x = {}",x);
println!("x = {x}");//最近のアップデートで{}の中に直接変数を入れられるようになった。{}内での演算はまだできない。
println!("x = {x:+10.5}");//出力を整形できる
println!("x = {x:?}");//デバッグモードで出力
+は符号を表示するという意味。10.5は小数点以下は5文字まで表示し、最低10文字分になるように空欄を挿入するという意味。
所有権と借用
Rustの変数には所有権という概念がある。ただ一つの変数だけが実際のデータの所有者になり得る。
let x = String::from("data");
let y = x;//xに格納されていたデータ"data"の所有権がyに移動した(moveした)
println!("x = {x}");//エラー!(なぜならxはデータの所有権を持っていないから)
let x = String::from("data");
let y = &x;//借用する(参照を取る)ことで所有権を放棄せずにデータを渡せる
let ref z = x;//これでもok
println!("x = {x}");
println!("y = {y}");
let mut x = String::from("data");
let y = &mut x;//変更可能な参照を取る
let ref mut z = x;//エラー! この書き方も可...だけど、可変参照は二つ以上存在出来ない
let ref z = x;//エラー! 可変参照がある場合は普通の参照も取れない
*y = y.replace("a","A");
println!("x = {x}");
let x = 10;
let y = x;//Copyトレイト(後述)が実装されている型は暗黙にコピーされる
println!("x = {x}");//コピーされただけなので所有権は失われていない
ちなみに構造体の中の特定のデータを借用すると構造体全体が借用されたように振る舞う(初心者の時にこれで沼った)
四則演算
let x = 5;
let y = 7.5;
let z = x * y;//エラー! 同じ型同士でしか演算出来ない
let z = x as f32 * y;//演算するためには型変換してやる必要がある
println!("z = {z}");
条件分岐
if 5>10{
todo!();//後で何かしらの処理に置き換えるよという意味
}else if 1<5{
todo!();
}else{
todo!();
}
()が不要な代わりに{}は必須
配列
let array = [0.0;10];//初期値が全部0.0で長さが10のf32の配列が定義される。
let mut vec = vec![0.0;10];//初期値が全部0で長さが10のf32の可変長配列が定義される。
let mut vec:Vec<f32> = Vec::new();//rustではコンストラクタの代わりにスタティックなnewなどと命名された関数を使う
let array = [1,2,3,4];//こういう定義もできる
println!("{array[3]}")//4番目を出力
ちなみにRustでは未定義の変数は存在出来ないようになっている。配列も、宣言時に必ず値が入る。
未定義の変数を表したい場合、後述のOptionを使う。
for
for i in 0..5{//0~4までがループする
println!("{i}");
}
for i in 0..=5{//0~5
println!("{i}");
}
let array = [1,3,5,2];
for item in array{//右辺はイテレータであればなんでも許可される
println!("{item}");
}
rustでのfor文は、イテレーターを処理するもの。0..5は、0~4を順番に返すイテレーターを生成する構文。
コードブロック
//{}は値を返す
let x = {
todo!();
10//最終行に;を付けずに書くとその値が戻り値として使われる
};
//if文も実行されたコードブロックの戻り値を返す
let y = if x>5 {"x is bigger than 5"} else {"x is not bigger than 5"};
関数定義
hoge();//定義の順番は問わない
fn hoeg(){
println!("hoge");
}
fn add(rhs:i32,lhs:i32) -> i32{
rhs + lhs//return不要。
}
fn sub(rhs:i32,lhs:i32) -> i32{
return rhs - lhs;//勿論returnすることもできる
}
//引数をmoveではなく借用にする
fn ref(ref string:String){
println!("{string}");
}
//これでも同じことができる
fn borrow(string:&String){
println!("{string}");
}
//上の二つは呼び出し方が変わる
//下だと明示的に参照だということを示せる
ref("aiueo".to_string());
borrow(&"aiueo".to_string());
外部クレートのインストール
クレートとは要するに外部パッケージのこと
Cargo.tomlのdependenciesの部分に以下のように追加する
[dependencies]
rand = "0.6"
これでrandクレートのインストールが出来た
https://crates.io
ちなみにここに存在するクレートだったらなんでもこの方法で導入できる
一旦練習問題
①0~100までの数を出力するけど、3の倍数の時には代わりにhoge、5の倍数の時には代わりにmoge、3の倍数でも5の倍数でもある時は代わりにhogemogeと出力するプログラムを書いてみよう
②0!から10!を求めて出力するプログラムを書いてみよう
③以下で定義される配列をソートして出力するプログラムを書いてみよう
use rand::Rng;
let mut rng = rand::thread_rng();
let mut array = [0;20];
for item in array.iter_mut(){
*item = rng.gen_range(0, 100);
}
enumとパターンマッチング
//enumは値を持つことができる
enum IP{
V4([u8;4]),
V6(String),
}
fn print_ip(ref ip:IP){
match ip{
IP::V4(x) => {println!("{x:?}")},
IP::V6(x) => println!("{x}"),//{}は省略可
_ => (),//何もマッチしなかったときに実行される。
}
}
let ip1 = IP::V4([123,200,255,255]);
let ip2 = IP::V6("240d:1a:c5:c100:9ccd:2942:b774:c1fc".to_string());
print_ip(ip1);
print_ip(ip2);
OptionとResultという二つの組み込みのenumがある。
//Optionはnullになり得る型を作る時に使う
let nullable_int:Option<i32> = Some(10);
let none:Option<i32> = None;
//Resultは関数のエラー処理に使う(戻り値として)
let result_ok:Result<i32,String> = Ok(10);
let result_err:Result<i32,String> = Err("".to_string());
中身を取り出すときにはmatchが必要かと思うかもしれないが、OptionやResultには組み込みのメソッドが用意されているのでそちらを使うと楽。
構造体
struct Student{
name:String,
id:u32,
}
let test_student = Student{
name:"test".to_string(),
id:0123456789,
};
//implでメンバを定義する
impl Student{
//Rustではコンストラクタという概念が無く、コンストラクタの代わりにスタティックなnew関数やcreate関数を使う。
fn new(_name:String,_id:u32) -> Student{
Student{
name:_name,
id:_id
}
}
fn new_advance(name:String,id:u32) -> Student{
//引数名とフィールド名が同じの時は省略可
Student{
name,
id,
}
}
//第一引数にselfを入れると他の言語で言うメソッドになる
//普通にselfを入れると呼び出し元から所有権をもぎ取ってしまうので&を付けて借用にする
fn get_id(&self) -> u32{
self.id
}
fn set_id(&mut self,new_id:u32){
self.id = new_id;
}
}
let student = Student::new("umehara".to_string(),2620211026);
//この二つは同じ意味
student.set_id(1234);
Student::set_id(&student,1234);
ちなみにenumにもstructと同じようにimplできる。
rustではデータと関数が完全に分離している。構造体と関数で名前空間を分けることとかもできる。
トレイト
他の言語で言うインターフェースのこと。rustでは共通の振る舞いをする構造体を作る時に継承は使わず、トレイトで全てを解決する。
Debugトレイトの実装要件 https://doc.rust-lang.org/std/fmt/trait.Debug.html
//(前略)
//DebugトレイトをStudentに実装する。
impl Debug for Student{
//VScodeならfnと打つと予測変換に表示される
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"name={},id={}",self.name,self.id)
}
}
//Debugトレイトを実装したのでstudentをデバッグモードで標準出力できるようになった。
println!("{student:?}");
ちなみにさっき名前だけ出てきたCopyトレイトだが、これはマーカートレイトと呼ばれるもので、何の関数も実装しない。何も実装しないが、そのトレイトが付いていると振る舞いが変わる。例えばCopyトレイトが実装されていると、"="する時に所有権を移動せずに暗黙的にコピーを行う。
継承
他の言語の継承とは意味が違う。Rustでは全自動でトレイトを実装してくれる機能のこと。
//この機能は関数内では使えないので関数外で定義すること。
#[derive(Debug,Clone)]
struct Student{
name:String,
id:u32,
}
let student = Student{
name:"test".to_string(),
id:0123456789
};
let cloned_student = student.clone();
println!("{student:?}");
これで全自動でDebugトレイトとCloneトレイトが実装される。ちなみにこの継承はマクロで実装されている。今回は紹介しないけどRustはマクロがかなり強力。実はprintln!とかvec!とかもマクロ。
トレイト境界
//これでDebugを実装している型ならばなんでも引数に取れる。
fn trait_bound_test(ref debagable_sometiong:impl Debug){
println!("{debagable_sometiong:?}");
}
trait_bound_test(10);
trait_bound_test(10.0);
trait_bound_test("123");
タプル
let tens = (10,10.0,"ten");
簡単にタプルが生成できるので、関数から複数の戻り値を返すのも簡単。
パターンマッチング(発展)
let tens = (10,10.0,"ten");
let (a,b,c) = tens;//これで分解できる。
println!("{a},{b},{c}");
let x = Some(10);
//パターンがマッチした時だけ内部が実行される。
if let Some(i) = x{
println!("{i}");
}
//可変借用を要求する
if let Some(ref mut i) = x{
*i = 30;
}
println!("{}",x.unwrap());
//数値もマッチできる(なぜか)
let x = 10;
match x{
1..=5 => (),//何もしない
6..=10 => x*=2,
_ => x = 0,
}
println!("{x}");
let student = Student::new("umehara".to_string(),2620211026);
//実はletは変数定義ではなくパターンマッチングをする物だった。
let Student{id:i,name:n} = student;
println!("{i},{n}");
クロージャ
let f = |lhs,rhs|{lhs*rhs};
println!("{}",f(1,2));
引数の型とか戻り値とかが型推論で決定される。
練習問題
行列の計算を行う構造体を作ってみよう。
足し算、掛け算、スカラー倍、行列式、転置、逆行列を定義出来たら完成。
ちなみにトレイトの実装という形で演算子のふるまいを定義できる。
参考
ジェネリクス
struct Pair<T> {
first:T,
second:T,
}
impl<T> Pair<T>{
fn new(fitst:T,second:T)->Self{
Pair<T>{
first,
second,
}
}
}
//Debugトレイト(+演算)を実装している型だけに限定する
impl<T:Debug> Pair<T>{
fn print(fitst:T,second:T){
println!("({first:?},{second:?})");
}
}
//型だけではなく数値も扱える。
struct arr<const i:usize>{
item:[i32;i],
}
最終問題(没)
問題作ったはいいけど難しすぎるので没にした。やってみる人は説明してないけどライフタイム注釈という概念が出てくるので頑張って。
i32を格納する片方向線形リストを実装してみよう。
片方向線形リストとは...
https://ja.wikipedia.org/wiki/連結リスト#:~:text=%5B編集%5D-,線形リスト,-%5B編集%5D
末尾へのデータの追加、削除とができるようにして、標準出力に出せるようになったら完成。
発展
①i32だけではなく全部の型に対応できるようにしてみよう
②イテレータを生成する関数を作ってfor文で使えるようにしてみよう
https://doc.rust-lang.org/std/iter/trait.Iterator.html これのnextだけ実装すれば良い。
③末尾以外にも追加、削除ができるようにしてみよう
ヒント
struct Node<'a>{
item:i32,
next:&'a mut Node<'a>,
}