Rustに入門する
そろそろRustのキャッチアップをしたい。
The Rust Programming Languageをやるのが良さそうなので頭からやっていく。
インストールからHello World, パッケージマネージャのcargoの解説。cargo OOOO
でなんでもできる感じ。
https://doc.rust-jp.rs/book-ja/ch01-00-getting-started.html
VSCodeにRustプラグインを入れた。これで補完とかAuto Formattingが効くようになる。
最近はFlutterとかPrettierのある環境、あとはGoとか書く時も全てAutoFormatting前提の世界線で生きてる。これがないとだるくてやってられんという感じ。
2章をやった。
let, mut, 外部Crate, shadow, match, expectとかを見た。
shadowingを使うと無駄に変数名に悩んだりしなくてよくなるので便利そう。詳しくはまだ解説されてないけどmatchは一般的なパターンマッチなのかな。まだ特にハマる部分はないのでサクサク次へ進む。
3章をやる。仕事後で疲れてるのでちょっとだけ。
セミコロンを付けないと式(値を返す)になり、つけると文(値を返さない)になる。
一般的な言語と変わらないので特筆すべきことはほぼなし。
4章やっていく。
所有権。Rustの目玉特性。
ヒープデータを管理することが所有権の存在する理由だと知っていると、所有権がありのままで動作する理由を 説明するのに役立つこともあります
ヒープの値ならこれがshallow copyにならない。s1が無効化されs2に完全にメモリの所有権が移る。
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // => Error
deep copyはclone()で可能。スタックの値(スカラー値とか)はデフォでdeep copyしてくれるから所有権の移動を気にしなくて良い。
関数への変数の引数渡しでも所有権が発動する。
let s1 = String::from("hello");
hoge(s1);
println!("s1: {}", s1); // => Error
let s2 = 5;
foo(s2);
println!("s2: {}", s2);
所有権を奪わせない方法。&で参照渡しをする。関数の引数に参照を取ることを借用と呼ぶ。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
// '{}'の長さは、{}です
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
参照は不変。だが、これで可変な参照も可能。
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
しかし可変な変数の参照をコピー出来るのは一つだけ。
let mut s = String::from("hello");
let s1 = &mut s; // 可変だけど一つ目なのでok
let s2 = &mut s; // Error!!
さらに不変の参照中は可変の参照ができない。
let mut s = String::from("hello");
let s1 = &s; // 不変なのでok
let s2 = &s; // 不変なのでok
let s3 = &mut s; // 可変で一つ目だけどs1,s2が不変の参照中なのでError!
実例。
fn main() {
let mut s1 = String::from("hello");
// 参照で渡したs1がhogeの返り値として参照で返り、それが不変のwordとして借用されている
let word = hoge(&s1[..]);
// => 結果としてs1はwordで不変の借用が発生しているので変更できない。よってここでエラーになる。
s1.clear();
println!("s1 after clear: {}", word);
}
fn hoge(s: &str) -> &str {
return &s[0..1];
}
5章をやる。構造体。
JSのspread演算子とかフィールド名の省略記法みたいなのがあるっぽい。
{:#?}
を使うとtoStringみたいなやつがなくてもprintで出力出来る。
メソッドは&self
を引数に渡す。
関連関数はclassメソッドみたいなやつ。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
6章やる。
Enum
直接値も定義できるから構造体を作ったりしなくてよくて便利。
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
以下は同じ。enumで定義すると一つのMessageという型で表現できる。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
struct QuitMessage; // ユニット構造体
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // タプル構造体
struct ChangeColorMessage(i32, i32, i32); // タプル構造体
enumはimplでメソッドも定義できる。もう構造体やん。
Rustにはnullが無い!!!!
代わりにOption<T>を使う。Some<T> or None のenumになってる。ドキュメントを初めて読んだがExampleもしっかりあってわかりやすい。
match
こんな感じ。
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
_ => ()
}
if let
これが等価。ただしmatchで得られていた包括性チェックは失われるので複数のチェックが必要な時はmatchを使った方が良さそう。
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = some_u8_value {
println!("three");
} else {
()
}
7章やる。
パッケージとクレート
パッケージはクレートを集めたもの。
- パッケージ
- クレート
- バイナリクレート(クレートルートはmain.rs)
- ライブラリクレート(クレートルートlib.rs)
- クレート
モジュール
クレート内のコードのグループ化。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
モジュールへのアクセス
pub fn eat_at_restaurant() {
// Absolute path
// 絶対パス
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
// 相対パス
front_of_house::hosting::add_to_waitlist();
}
モジュールはデフォルトで非公開。
pub
を使って公開することができる。
構造体はフィールドレベルで公開/非公開を指定できる。一方でEnumはフィールドレベルではコントロールできない。
mod back_of_house {
pub struct Breakfast {
pub toast: String, // 公開
seasonal_fruit: String, // 非公開
}
}
mod back_of_house {
pub enum Appetizer {
Soup, // コントロールできないので公開
Salad,// コントロールできないので公開
}
}
use
モジュールを読み込む。
// 例えば絶対パス呼び出しが
crate::front_of_house::hosting::add_to_waitlist();
// これで呼べるようになる
use crate::front_of_house::hosting;
hosting::add_to_waitlist()
// これは慣習に反する。ローカルスコープで定義された関数なのか親で定義された関数なのか区別できないので。
use crate::front_of_house::hosting::add_to_waitlist;
add_to_waitlist()
// ただしこれはok
use crate::front_of_house::hosting::add_to_waitlist as foo_add_to_waitlist;
foo_add_to_waitlist()
// これと
use std::cmp::Ordering;
use std::io;
// これは等価。
use std::{cmp::Ordering, io};
// globでの読み込みも可能
use std::*;
モジュールのファイル分割
modで定義した名前と同じファイル名のファイルを使う。そのファイル名と同じ名前のディレクトリを作って、その配下にクレートを色々作っていく感じ?
ちょっとイメージが完全じゃない。このエントリ読むと具体例とかわかるかも。
binaryとlibraryパッケージが混在してる時の例
ポイントはmain.rsのルートのスコープはCargo.tomlのnameになるということ。以下の場合ではfooがmain.rsのルートのスコープになる。よってnyanパッケージをmain.rsで使いたい場合はuse nyan
ではなくuse foo::nyan
と書かなければいけない。
├── Cargo.lock
├── Cargo.toml
└── src
├── lib.rs
├── main.rs
└── nyan
├── attr.rs
└── mod.rs
// Cargo.toml
[package]
name = "foo"
// main.rs
use foo::nyan
fn main() {
nyan::call();
}
// lib.rs
pub use nyan;
// nyan/mod.rs
pub use attr;
// nyan/attr.rs
pub fn call() {
println!("nya----n");
}
8章やる。
ベクタ
ベクタは可変のリスト。
let v: Vec<i32> = Vec::new();
println!("v: {:#?}", v);
let mut vv = vec![1, 2, 3];
vv.push(4);
println!("vv: {:#?}", vv);
let vvv: Option<&i32> = vv.get(3);
match vvv {
Some(..) => {
println!("vvv: {:#?}", vvv);
}
None => {
println!("vvv: {:#?}", vvv);
}
}
for i in &mut vv {
*i += 50;
println!("vvv:{}", i);
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
for i in &row {
match i {
SpreadsheetCell::Int(v) => {
println!("Int: {}", v)
}
SpreadsheetCell::Float(v) => {
println!("Float: {}", v)
}
SpreadsheetCell::Text(v) => {
println!("Text: {}", v)
}
}
}
スライス
- 参照外し型強制??
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1の所有権を奪い、s2の中身のコピーを追記し、結果の所有権をs3に返す
// format!は所有権を奪わない
format!("{}{}", s1,s2);
- 文字列の長さは文字のバイト数で決まる。よって文字によって1文字1スペースではないので添字アクセスはできない。
let len = String::from("Здравствуйте").len(); // 12文字っぽいけどキリル文字は2バイトなので24になる
// スライスでの範囲アクセスは可能
let hello = "Здравствуйте";
let s = &hello[0..4]; // &strで4バイトが返る
ハッシュマップ
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = 1;
let mut map = HashMap::new();
map.insert(field_name, field_value);
map.insert(String::from("Favorite color"), 10); // 上書き
map.entry(String::from("Favorite color")).or_insert(50); // キーに値がない時は50をセットする
for (key, value) in &map {
println!("{}: {}", key, value);
}
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
// キーに値がなければ0を値にセットし、あればその値を返す
let count = map.entry(word).or_insert(0);
// &mut T を返すからcountでは*を使って参照外しをしてインクリメントしてる
*count += 1;
}
println!("{:?}", map); // => {"world": 2, "wonderful": 1, "hello": 1}
9章をやる。
回復可能なエラー
- Result<T, E>
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(ref error) if error.kind() == ErrorKind::NotFound => {
match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => {
panic!(
//ファイルを作成しようとしましたが、問題がありました
"Tried to create file but there was a problem: {:?}",
e
)
},
}
},
Err(error) => {
panic!(
"There was a problem opening the file: {:?}",
error
)
},
};
}
// これで十分な時もある
let f = File::open("hello.txt").expect("Failed to open hello.txt");
// ?演算子
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
回復不能なエラー
- panic!
10章やる。
ジェネリクス
普通のジェネリクス。特に変わったところはなさそう。
Rustのジェネリクスはジェネリクスのコードを指定された値の型から推測して型が指定されたコードに置き換えてコンパイル(=単相化)するので実行速度は変わらないまま遅くならない。
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn get_x(&self) -> &T {
&self.x
}
fn get_y(&self) -> &U {
&self.y
}
}
impl Point<f32, f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
let x = integer.get_x();
let y = integer.get_y();
println!("(x,y) = ({},{})", x, y);
let x = float.get_x();
let y = float.get_y();
println!("(x,y) = ({},{})", x, y);
float.distance_from_origin();
// integer.distance_from_origin(); f32じゃないので使えない
}
トレイト
振る舞い(メソッド)まとめ。
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
// Summary traitを実装している引数は全て受け取れる
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// SummaryとDisplay traitを実装している引数のみ受け取れる
pub fn notify(item: &(impl Summary + Display)) {
}
// whereを使ってこういうのもいける
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}
戻り値をtraitにすることで型を指定しなくて良い関数を作れる。
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
ライフタイム
ライフタイムは参照が有効になるスコープ。初めての概念だしライフタイム注釈記法も見慣れないので難しい。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
小さい方のライフタイムが使われる。
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}
これはコンパイルエラーになる。
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
// longestの戻り値のライフタイムはより小さい方のstring2のライフタイムになってる
result = longest(string1.as_str(), string2.as_str());
}
// resultはstring2と同じスコープのライフタイムになっている
// なのでそれより外でresultを使用できない
println!("The longest string is {}", result);
}
常に使用可能なライフタイムは'static
を使う。
let s: &'static str = "I have a static lifetime.";
ジェネリクス/トレイト境界/ライフタイム全部盛り
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
// アナウンス!
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
11章やる。
Rustでのテスト。特に変わったところはなかった。
こんな感じでaddが付くテストだけ実行できたりするの便利。
$ cargo test add
単体テストは、テスト対象となるコードと共に、srcディレクトリの各ファイルに置きます。 慣習は、各ファイルにtestsという名前のモジュールを作り、テスト関数を含ませ、 そのモジュールをcfg(test)で注釈することです。
結合テストを作成するには、 まずtestsディレクトリが必要になる。プロジェクトディレクトリのトップ階層、srcの隣にtestsディレクトリを作成。
extern crate adder;
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
具体例はこれを見れば十分そう。
12章をやる。
1~11章までで学んだ範囲で実践としてコマンドラインツールを作る。grepの簡易版。
日本語版テキストにextern crate minigrep
と書かれていたが、これはいらない。
英語版のテキストを確認したらコードが少し違っていた。動かなくてちょっと嵌った。
// src/main.rs
use std::env;
use std::process;
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
if let Err(e) = minigrep::run(config) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
// src/lib.rs
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
pub struct Config {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query,
filename,
case_sensitive,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let mut f = File::open(config.filename)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
let results = if config.case_sensitive {
search(&config.query, &contents)
} else {
search_case_insensitive(&config.query, &contents)
};
for line in results {
println!("{}", line);
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn on_result() {
let query = "duct";
let contents = "\
Rust;
safe, fast, productive.
Pick three.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_sensitive() {
let query = "duct";
// Rust
// 安全かつ高速で生産的
// 三つを選んで
// ガムテープ
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
// (最後の行のみ)
// 私を信じて
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
13章やる。
let some_closure = |x| x;
let s = some_closure(String::from("hey")); // この時点で引数と戻り値がStringに推論される
let i = some_closure(1); // のでこれはErrorになる。
let x = vec![1, 2, 3];
let equal_to_x = |z| z == x;
println!("{:?}", x); // これは実行できる。クロージャは自分と同じスコープの変数にアクセスできるので。
let y = vec![1, 2, 3];
let equal_to_y = move |z| z == y;
println!("{:?}", y); // これはエラー。moveで所有権がクロージャ内に移動してしまうので。
Iteratorトレイトを実装して構造体にnextメソッドを使えるようにする。
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
これによってこの構造体はIteratorトレイトを実装してる全てのメソッドを使えるようになる。
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
assert_eq!(18, sum);
イテレータはゼロコスト抽象化なので変わらず高速。
13~19章は読んで軽くてを動かすだけで終わりにした。
スマートポインタはちょっと難しくて理解微妙。マルチスレッド処理に関してはGolangの方が楽に使えたなという感じだった。
20章はちゃんと実装する。
extern crate hello;
use hello::ThreadPool;
use std::fs::File;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::thread;
use std::time::Duration;
fn main() {
let listner = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::new(4);
for stream in listner.incoming().take(2) {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
}
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
let get = b"GET / HTTP/1.1\r\n";
let sleep = b"GET /sleep HTTP/1.1\r\n";
let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else if buffer.starts_with(sleep) {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};
let mut file = File::open(filename).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let response = format!("{}{}", status_line, contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
enum Message {
NewJob(Job),
Terminate,
}
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Message>,
}
type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(Message::NewJob(job)).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
println!("Sending terminate message to all workers.");
for _ in &mut self.workers {
self.sender.send(Message::Terminate).unwrap();
}
println!("Shutting down all workers.");
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv().unwrap();
match message {
Message::NewJob(job) => {
println!("Worker {} got a job; executing.", id);
job();
}
Message::Terminate => {
println!("Worker {} was told to terminate.", id);
break;
}
}
});
Worker {
id,
thread: Some(thread),
}
}
}