お題
Rustビギナーは必ずハマるという「借用チェッカとの戦い」の記録。
随時、更新予定。
まずは、自分が簡単なツールを作成しようとした時↓に遭遇したものから挙げていく。
・簡単なツール作成を通してRustを学ぶ
・【改善編】簡単なツール作成を通してRustを学ぶ
Rustのエラーページに書かれたものを網羅(それならそもそもそのページ見ればいい話)するより、
実際に何かしらツール作りをする過程で遭遇するものを挙げていった方が参考になるのでは?(作るツールやらアプリやらの特性によっても遭遇するエラーは変わるかもしれないけど)
ちなみに、単に、こう書くと「叱られる」という事実と、こう書くと回避(よい回避方法かまでは未検証)できるという事実だけを列挙。
なにしろ自分もRustはじめて数週間なので、間違っているかもしれない理屈をだらだら書くよりは実際に動かして起きた事実のみを列挙した方がよいかと。
編集履歴
2020-09-20 「E0038」と「E0277」についてコメントもらったので修正
対象者
Rustの勉強はじめてチュートリアルを写経くらいはしたけど、実際に自分で書き始めたらRustコンパイラにいろいろ叱られ始めた人。
実装・動作確認端末
# OS - Linux
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
# 言語バージョン
$ rustc --version
rustc 1.46.0 (04488afe3 2020-08-24)
$
$ cargo --version
cargo 1.46.0 (149022b1d 2020-07-17)
# IDE - IntelliJ IDEA
IntelliJ IDEA 2020.2.1 (Ultimate Edition)
Build #IU-202.6948.69, built on August 25, 2020
実践
[E0382] よそに渡した変数は、もう使えないよ。
たぶん一番ベタなやつ。
Rust以外の言語やってて、ここで怒られたことないから、(聞いてはいたけど)初見でビックリする。
ソース
fn main() {
let name = String::from("taro");
println!("{}", name);
let u = User { name };
println!("{}", u.name);
println!("{}", name); <--- 叱られる。
}
struct User {
name: String,
}
事象
name
が、最初はprintln
できたのに、構造体User
に渡したあとはprintln
できない。
こんなふうに叱られる。
borrow of moved value: `name` E0382 value borrowed here after move
回避策
fn main() {
let name = String::from("taro");
println!("{}", name);
let u = User { name: &name };
println!("{}", u.name);
println!("{}", name);
}
struct User<'a> {
name: &'a String, <--- 実体でなくて参照をもらう形にする。
}
[E0004] パターンは網羅しないといけないよ。
これは、ちょろい。
(使ってるIDE(とプラグイン)によるかもだけど)「こうしたら?」って解決策を提示してくれるので従うだけで解決。
ソース
fn main() {
let x = 1;
match x { <--- 叱られる。
0 => println!("zero"),
1 => println!("one"),
2 => println!("two"),
}
}
事象
パターンマッチは取りうる値を網羅して列挙しないといけないのです。
こんなふうに叱られる。
Match must be exhaustive [E0004]
回避策
もちろん今回のケースで全数値を並べていくわけにはいかないので、「その他」すべての扱いなら「_ => ~~
」でいいんです。
fn main() {
let x = 1;
match x {
0 => println!("zero"),
1 => println!("one"),
2 => println!("two"),
_ => println!("other"), <--- これ、ちゃんと補ってあげて。
}
}
[E0038] トレイトオブジェクトを使うときのルールに則ってないよ。
トレイトオブジェクトは、 指定したトレイトを実装するある型のインスタンスを指す。
以下のように、なんとなく「Javaのインターフェースみたいなもんでしょ?」という感じで使うと、叱られる。
ソース
fn main() {
let g = Get {};
exec(g);
}
trait Command {
fn run();
}
struct Get {}
impl Command for Get {
fn run() {
println!("Get!");
}
}
fn exec(cmd: Command) { <--- 叱られる。
cmd.run();
}
事象
こんなふうに叱られる。
the trait `Command` cannot be made into an object E0038
Help: consider turning `run` into a method by giving it a `&self`
argument or constraining it so it does not apply to trait objects
[2020-09-20 コメントもらったので修正] 回避策
今回のサンプル事例の書き方だと、Rustのコンパイラは「トレイトオブジェクト
(ポリモーフィズムの中でも動的ディスパッチ
をサポートするための機能)」でやろうとしていると判断する様子。
ただ、目的としてはCommand
トレイトを実装する何かが渡せればいい、そして、プログラム中で渡す構造体は既に決まっている、ということで、実は「トレイト境界
(静的ディスパッチ
をサポートするための機能)」を使えばよかったらしい。
※このあたりの話は下記参照。(公式ドキュメントでは第2版を見るよう促されるけど、今回事例で言うと下記がわかりやすい。)
https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/trait-objects.html
fn main() {
let g = Get {};
exec(&g);
}
trait Command {
fn run(&self);
}
struct Get {}
impl Command for Get {
fn run(&self) {
println!("Get!");
}
}
fn exec<T: Command>(cmd: &T) { <-- ジェネリクスにする。
cmd.run();
}
[E0277] トレイトじゃあ、サイズがわからないよ。
ソース
fn main() {
let g = Get {};
exec(g);
}
trait Command {
fn run(&self);
}
struct Get {}
impl Command for Get {
fn run(&self) {
println!("Get!");
}
}
fn exec(cmd: Command) { <--- 叱られる。
cmd.run();
}
事象
こんなふうに叱られる。
the trait bound `dyn Command: std::marker::Sized` is not satisfied [E0277]
`dyn Command` does not have a constant size known at compile-time
トレイトだとコンパイル時にサイズがわからない(具体的に渡されるトレイトオブジェクト?がわからない)のがダメらしい。
↓を見ると、またニュアンスが違うんだけど・・・。
回避策
IDEから提示される回避案は複数あった。
1つ目
Box
で囲ってしまえと言われた。
Boxについては以下参照。
https://doc.rust-jp.rs/book/second-edition/ch15-01-box.html
fn main() {
let g = Box::new(Get {}); <--- 当然、渡す方も囲わないといけない。
exec(g);
}
trait Command {
fn run(&self);
}
struct Get {}
impl Command for Get {
fn run(&self) {
println!("Get!");
}
}
fn exec(cmd: Box<Command>) { <--- はい、この通り。
cmd.run();
}
2つ目
参照にしちゃえと言われた。
mut
が必要ないなら、こちらの方が楽かな。
fn main() {
let g = Get {};
exec(&g); <--- もちろん渡す方も参照で。
}
trait Command {
fn run(&self);
}
struct Get {}
impl Command for Get {
fn run(&self) {
println!("Get!");
}
}
fn exec(cmd: &Command) { <--- たしかにサイズの問題は解消だね。
cmd.run();
}
[2020-09-20 コメントもらったので修正] Warningへの対処
上記の対処によりコンパイルエラーは解消されるものの以下のように警告は残っている。
Trait objects without an explicit 'dyn' are deprecated
トレイトオブジェクトを使う時は dyn
を付けて明示的にするのがお作法らしい。
1つ目の事例だと、
fn exec(cmd: Box<dyn Command>) {
cmd.run();
}
2つ目の事例だと、
fn exec(cmd: &dyn Command) {
cmd.run();
}
のようにする。
[E0106] ライフタイムが指示されてないよ。
これも、Rust以外の言語では見ない概念(少なくともコードではっきり明示しろとは言われない)なので、ハマる。
ライフタイムについては以下参照。
https://doc.rust-jp.rs/rust-by-example-ja/scope/lifetime.html
ソース
fn main() {
let s = SomeStruct { name: "test" };
println!("{:?}", s);
}
# [derive(Debug)]
struct SomeStruct {
name: &str, <--- 叱られる。
}
事象
こんなふうに叱られる。
missing lifetime specifier E0106
expected named lifetime parameter Help: consider introducing a named lifetime parameter
回避策
fn main() {
let s = SomeStruct { name: "test" };
println!("{:?}", s);
}
# [derive(Debug)]
struct SomeStruct<'a> { <--- 構造体本体にも必要。
name: &'a str, <--- ライフタイムを明示しろというので、明示してやる。
}
[E0515] すぐに消える情報の参照を関数から返すのはダメだよ。
Golangで書いてたら、こんな感じのコード、ほうぼうで書くと思うのだよね・・・。
ソース
fn main() {
let s = new_some_struct("test");
println!("{:?}", s);
}
# [derive(Debug)]
struct SomeStruct<'a> {
name: &'a str,
}
fn new_some_struct(name: &str) -> &SomeStruct {
&SomeStruct { name } <--- 叱られる。
}
事象
こんなふうに叱られる。
cannot return reference to temporary value E0515
returns a reference to data owned by the current function
回避策
fn main() {
let s = new_some_struct("test");
println!("{:?}", s);
}
# [derive(Debug)]
struct SomeStruct<'a> {
name: &'a str,
}
fn new_some_struct(name: &str) -> SomeStruct {
SomeStruct { name } <--- おとなしく、参照は外してしまおう。
}
[E0597] 参照で渡すその値は、そんなに長く生きられないよ。
ソース
fn main() {
let mut x = &SomeStruct { name: "xxx" };
{
let y = SomeStruct { name: "yyy" };
x = &y; <--- 叱られる。
}
println!("{:?}", x);
}
# [derive(Debug)]
struct SomeStruct<'a> {
name: &'a str,
}
事象
こんなふうに叱られる。
`y` does not live long enough E0597
borrowed value does not live long enough
回避策
ブロックスコープは、本当にそのブロックの中で完結するコードを収めましょう。
fn main() {
let mut x = &SomeStruct { name: "xxx" };
let y = SomeStruct { name: "yyy" };
x = &y; <--- ブロックを取っ払えば、同時にスコープも消える。
println!("{:?}", x);
}
# [derive(Debug)]
struct SomeStruct<'a> {
name: &'a str,
}
まとめ
「所有権」と「ライフタイム」について熟知するまでは、ひたすらRustコンパイラに叱られ続けるんだろうなぁ。