この記事は Wano Group Advent Calendar 2019 の14目の記事となります。
先日の3月に開発合宿に行ってきました。というわけで、早速レポートしたいと思います。
なんか、すみません。
※Perlのネタを書くつもりでしたが、そちらはPerl Advent Calendar に書くことにします。
開発合宿
Wanoグループでは開発合宿として3月あたりに、エンジニアで集まって開発合宿に行っています。
詳しくは、こちらを参照してください。
僕は、社内でいつの間にかRustのコードができあがっていたので、それを読めるようならねばということで、一夜漬けでRustのドキュメントをひたらすら読んで試すということをしていました。
9ヶ月前にガーッとやったやつを、また、ザーッと見直して、ブログに落としているので、多分、色々間違っている可能性が否定できません...。
参考にしたドキュメント
タイトルの通り、The Rust Programming Languageを読み進めました。
コマンドについて
- rustup doc ... ドキュメントが立ち上がる
- cargo doc --open ... 自分で作ったコードのドキュメントが立ち上がる
- rustc main.rs ... main というバイナリができあがります
main
とりあえず、実行したいなら main 関数を作ります。
きっと、標準入出力も扱うでしょうから、std::io
も use
しましょう。
use std::io;
fn main() {
}
変数宣言
let で宣言できます。型を明示的に指定しなければ、型推論してくれます。
let a = 1; // 型推論
let b :i32 = 1; // 明示
この宣言では、変数は、immutable(不変)なので、変更できません。
一度宣言した変数に、別の値を割り当てることは出来ません。
変更したい場合は、 mut
を付けます。
let mut a = 1; // 変更可能な変数には mut を付ける
a *= 10; // 変更できます
文字列
文字列は下記のようにします。両方同じ。
let s = String::from("abc");
let h = "へろー".to_string();
chars
でイテレータが返ります。
for c in s.chars() { // 文字単位
println!("{}",c)
}
for b in s.bytes() { // バイト単位
println!("{}",b)
}
文字連結
push_str
を使います。
let mut s = String::from("はろー");
s.push_str(", わーるど"); // 文字の連結
+
も使えますが、その場合、右側に参照を渡します。
let s2 = String::from("! あいうえお");
let s = s + &s2; // + も使えるけど、右側には参照を渡す
文字列のスライス
let v = s.chars().nth(0).expect("「は」が取れるはず"); // expect は、失敗した際に panicする時に出す文字列
println!("{}", v);
オーバーすると、ぱにくります。
let v = s.chars().nth(100).expect("ぱにくる");
文字列から数字
unwrap()
というのを使ってますが、parse
が Option型というのを返してくるためです(後述)。
let num = "123".to_string().parse::<i32>().unwrap();
let num2 = 123;
if num == num2 {
println!("同じ")
}
ループ
for は既に書きましたが、無限ループ。
let mut cnt = 0;
let n = loop { // 無限 loop
cnt += 1;
println!("ループ!: {}回目", cnt);
if cnt == 10 {
break cnt // break を loop の戻り値として返せる
}
};
println!("breakの戻り!:{}", n);
Option型/Result型
前述のparseのように失敗する可能性のあるものが、Result型とかOption型とかを返す場合があります。
下の場合、nth もサイズを超えると失敗するので、Option型を返してきます。
match
を使って処理できます。
let r = s.chars().nth(100); // Option を返してくる
let v = match r {
Some(_) => s, // なんか返ってきたら
None => String::from("NULL"), // オーバーしたら"NULL"って返す
};
所有権
Rustには、所有権
という概念があります。
pass_string
という関数があったとして、それに変数 v
を渡した場合。
let s = pass_string(v);
println!("{}", s);
println!("{}", v); // pass_string で moveされているので死ぬ
所有権が移ってしまったものは利用できません。
下記のように、関数に参照渡しをした場合は、死にません。
let v = pass_ref_string(&s);
println!("{}", s); // 参照渡しなので死なない
println!("{}", v);
pass_string, pass_ref_string の定義は下記。
fn pass_string (s: String) -> String {
s
}
fn pass_ref_string (s: &String) -> &String {
&s
}
immutableなので、関数の中で変更できませんが、下記の modify_string のような定義であれば変更できます。
fn modify_string (mut s: String) -> String {
s.push_str("あああ");
s
}
以下のように使います。
let v = String::from("えええ");
println!("{}", modify_string(v)); // modify_string 側で mutを付けると変更できる
if s == "NULL" { // v は modify_string で moveされているので使えない
println!("True");
} else {
println!("False");
}
enum
宣言。
enum Breakfast {
Rice,
Bread,
}
使い方。
let rice = Breakfast::Rice;
let bread = Breakfast::Bread;
let breakfast = [rice, bread]; // 配列
for r in breakfast.iter() {
// enum をパターンマッチ
match r {
Breakfast::Rice => {
println!("米がたべたい")
},
Breakfast::Bread => {
println!("パンがたべたい")
},
}
}
文字列を取る enum を使う
宣言。
// 引数を取れる Debug trait を利用
#[derive(Debug)]
enum Breakfast2 {
Kind(String),
Calorie(u32),
}
使い方
let b = Breakfast2::Kind(String::from("炊き込みご飯"));
let c = Breakfast2::Calorie(1000);
// Breakfast2 は std::fmt::Display を実装していないので、"{}"は使えない
// #[derive(Debug)] しているので、"{:?}" は使える
println!("朝食の種類:{:?}", b);
println!("カロリー:{:?}", c);
自分で、std::fmt::Display を実装する
enum Breakfast3 {
Kind(String),
Calorie(u32),
}
// 自分で、std::fmt::Display を実装
// 下記からパクったけど、IDE上で赤くなるけど、特に文法エラーではない
// https://users.rust-lang.org/t/printing-enum-values/21002
impl std::fmt::Display for Breakfast3 {
fn fmt(&self, f : &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Breakfast3::Kind(v) => v.fmt(f),
Breakfast3::Calorie(v) => v.fmt(f),
}
}
}
使い方。
// Breackfast3
let b = Breakfast3::Kind(String::from("炊き込みご飯"));
let c = Breakfast3::Calorie(1000);
// Breackfast3 は std::fmt::Display を実装しているので、"{}"が使える
println!("朝食の種類:{}", b);
println!("カロリー:{}", c);
モジュール
person と animal のモジュールを作ってみた。
// person モジュール
mod person {
pub struct Person {
pub name: String, // pub を付けないと private です
height: u32,
weight: u32,
weapon: String,
}
impl Person {
// コンストラクタだが、 new に特に意味はない
pub fn new(name: String, height: u32, weight: u32, weapon: String) -> Person {
Person {
name,
height,
weight,
weapon,
}
}
pub fn height(&self) -> u32 {
self.height
}
pub fn weight(&self) -> u32 {
self.weight
}
}
// Person に Creature trait を実装する
impl super::Creature for Person {
fn walk(&self) {
println!("人として歩きます")
}
fn weapon(&self) -> String {
self.weapon.clone()
}
}
}
// animal モジュール
mod animal {
pub struct Animal {
pub name: String, // pub を付けないと private です
height: u32,
weight: u32,
weapon: String,
}
// コンストラクタだが、 new に特に意味はない
impl Animal {
pub fn new(name: String, height: u32, weight: u32, weapon:String) -> Animal {
Animal {
name,
height,
weight,
weapon,
}
}
pub fn height(&self) -> u32 {
self.height
}
pub fn weight(&self) -> u32 {
self.weight
}
}
// Animal に Creature trait を実装する(walk はデフォルトにして、weapon() だけ実装)
impl super::Creature for Animal {
fn weapon(&self) -> String {
self.weapon.clone()
}
}
}
// Tに特に意味はない(Uでもいい。何らかの型という意味)。ここ(T:Crature)では、Creature trait を実装している型のこと
fn what_weapon_has<T:Creature>(t :T) -> String {
t.weapon()
}
使ってみる。
// モジュール person。new 関数を construct として使っている(が、別に new が特別なワードではない)
let p = person::Person::new(String::from("Atsushi Kato"), 173, 65, String::from("fist"));
println!("名前: {}", p.name); // name は pub なのでアクセス可能
println!("身長: {}", p.height());
println!("体重: {}", p.weight());
// モジュール animal. new 関数を construct として使っている(が、別に new が特別なワードではない)
let a = animal::Animal::new(String::from("Monkey"), 133, 45, String::from("fang"));
println!("名前: {}", a.name); // name は pub なのでアクセス可能
println!("身長: {}", a.height());
println!("体重: {}", a.weight());
// walk メソッドを使ってみる。これは trait(go の interface的なもの) Creature を実装している
// trait では default を定義できるので、animal では実装していない
p.walk();
a.walk();
// whtat_weapon_has は、Creature の trait を満たしていれば、使うことが出来る
println!("{}",what_weapon_has(p));
println!("{}",what_weapon_has(a));
ベクタ
// ベクタ
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v[0] = 100;
println!("{:#?}", v);
let mut v = vec![0 ; 10]; // 0 ; 10 で 0 を 10こ作る
v[0] = 101;
v.push(1);
v.push(2);
println!("{:#?}", v);
let r = v.get(100);
let r = match r {
Some(_) => n,
None => { // 式も書けるよ
println!("取得失敗");
0
},
};
println!("戻り:{}", r);
ハッシュ
// HashMap
use std::collections::HashMap;
let mut hash = HashMap::new();
let n1 = 10;
let n2 = 20;
hash.insert("あいうえお".to_string(), n1);
hash.insert("かきくけこ".to_string(), n2);
println!("{:#?}", hash);
println!("{:#?}", hash.get(&"あいうえお".to_string())); // get するときはキーの名前は参照で渡す必要がある
// ハッシュの値を変更
// あれば、値を変更する and_modify は無名関数(ココでは、|v| *v = 10000)が引数(複数行の時は、 {} を使う)を取る
hash.entry("あいうえお".to_string()).and_modify(|v| *v=10000);
// なければ、値を入れる or_insert はそのまま値を取れる
hash.entry("ほげ".to_string()).or_insert(100);
println!("{:#?}", hash);
ファイル入出力
ファイルの読み書きをする場合は、
use std::fs::File;
して下さい。
下記、サンプル。
let f = File::open("/etc/passwd");
let _f = match f {
Ok(file) => file,
Err(error) => {
panic!("ファイルopen時にエラー {:?}", error)
}
};
// 面倒くさい場合
let _f = File::open("/etc/passwd").unwrap();
// 面倒くさいけど、メッセージくらいは出したい
let _f = File::open("/etc/passwd").expect("ファイルオープンに失敗");
let result = can_open_file("/etc/passwdz".to_string());
match result {
Ok(true) => println!("ファイルOpen可能"),
Ok(false) => println!("ファイル開けませんよ"), /* 意味ないのでは */
Err(e) => println!("ファイル開けません: {}", e)
}
let result = can_open_file_2("/etc/passwd".to_string());
match result {
Ok(true) => println!("ファイルOpen可能"),
Ok(false) => println!("ファイル開けませんよ"), /* 意味ないのでは */
Err(e) => println!("ファイル開けません: {}", e)
}
}
fn can_open_file(path: String) -> Result<bool, io::Error> {
let f = File::open(path);
return match f {
Ok(_file) => Ok(true),
Err(e) => Err(e),
};
}
fn can_open_file_2(path: String) -> Result<bool, io::Error> {
let _f = File::open(path)?;
Ok(true)
}
時間切れ
もう日が変わってしまうので、今日はここまで。
合宿以来全然触ってなかったので、すっかり忘れてしまっているのですが、面白みのある言語だと思うので、また機会をみてやってみようかと思います。
書いたコード
下記に上がっています。
謝罪
近年稀に見る、雑な記事で大変申し訳無く思っております...m(__)m