所有権と借用
所有権 Ownership
- 所有権とは
- 所有権と値のライフスコープ
- 所有権の移動
- コピーセマンティクス
- Copyトレイト実装型
- 基本型 (bool, i32, ...)
- 参照型
- Copyトレイト実装型
- ムーブセマンティクス
- コピーセマンティクス
所有権とは
値を保持できる変数は、必ず1つであるという制約のこと。
#[derive(Debug)] struct V(i32); // 変数aが値V(0)の所有者となる let a = V(0); // 変数bにaを代入すると、値V(0)の所有者が変数bに移る let b = a; // コンパイルエラー dbg!(a);
所有権と値のライフスコープ
「値の所有者は1つである」という制約から所有する変数のスコープが値のライフタイムとなる。
struct V;
let a = V;
{
let b = a; // 値Vのライフタイム = 変数bのスコープ
}
所有権の移動
代入によって所有権が移動するかどうかは、Copyトレイトを実装しているかどうかによって決まる。
Copyトレイト
Cloneのサブトレイトであり、マーカートレイト。
#[derive(Debug)]
#[derive(Clone, Copy)]
struct V;
let a = V;
{
// 値がCopyされるので、aは所有権を失わない
let b = a;
}
// aは有効
dbg!(a);
主なCopyトレイト実装型
- 基本型
- 参照型
&T
参照型&Tはコピーセマンティクス
fn get_str(str: &str) {
let str1 = str; // strがコピーされる
let str2 = str1; // str1がコピーされる
dbg!(str2); // OK
dbg!(str1); // OK
dbg!(str); // OK
}
値の借用 Borrow
&演算子(Borrow)によって、参照型(&T)を生成することを「値を借用する」という。
このとき、値の所有権が移動しない。
&演算子(Borrow)は、参照型を生成する。
let a = String::from("Hello World");
// &演算子によって参照型を生成する => 変数aの値を借用する => 所有権は移動しない
let ref_a: &String = &a;
借用ルール
- mutable参照は一度に1つまでしか作れない
- mutable参照が存在する場合は、借用できない
fn main() {
let mut s = String::from("Hello");
let mut ref_s = &mut s;
// NG
let mut ref_s2 = &mut s;
ref_s.push(' ');
ref_s2.push_str("World");
/*
error[E0499]: cannot borrow `s` as mutable more than once at a time
|
3 | let mut ref_s = &mut s;
| ------ first mutable borrow occurs here
...
6 | let mut ref_s2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
7 |
8 | ref_s.push(' ');
| ----- first borrow later used here
*/
}
参照
References and Borrowing - The Rust Programming Language
関連
ポリモーフィズム with トレイトオブジェクト
ポリモーフィズム (Polymophism)
deepLによる翻訳
ポリモーフィズム 多くの人にとって、ポリモーフィズムは継承と同義語です。しかし、実際にはもっと一般的な概念であり、複数の型のデータを扱うことができるコードを指します。継承の場合、それらの型は通常サブクラスです。
その代わりに、Rust はジェネリックを使ってさまざまな可能性のある型を抽象化し、 trait bounds を使ってそれらの型が提供しなければならないものに制約を与えます。これを bounded parametric polymorphism と呼ぶこともあります。
参照
Object Oriented Programming Features of Rust - The Rust Programming Language
トレイトオブジェクト
Trait objects
動的サイズ決定型 (DSTs)
トレイトオブジェクトは、以下のポインタを持つワイドポインタ(動的サイズ決定型)
- 値へのポインタ
- 仮想メソッドテーブル
トレイトオブジェクトの形
Box<dyn SomeTrait>&dyn SomeTrait
トレイトオブジェクトの目的
動的ディスパッチを実現する。
trait Printable {
fn stringify(&self) -> String;
}
impl Printable for i32 {
fn stringify(&self) -> String {
self.to_string()
}
}
// Box
fn print(a: Box<dyn Printable>) {
println!("{}", a.stringify());
}
// 参照
fn print2(a: &dyn Printable) {
println!("{}", a.stringify());
}
fn main() {
print(Box::new(10));
print2(&20);
}
関連
トレイトオブジェクトになれないトレイト - あるマのメモ書き
関連型を持つトレイトオブジェクトを宣言する - あるマのメモ書き
参照
https://doc.rust-lang.org/reference/types/trait-object.html
https://qiita.com/hadashiA/items/d0c34a4ba74564337d2f#comment-99db3667a99310a68644
&Foo はトレイトオブジェクトですよ。 トレイトオブジェクトで重要なのは、仮想関数テーブルとオブジェクト実体へのポインタの組であるという点です。トレイトオブジェクトそのものはトレイト境界が実装された型の参照からキャストすればいくらでも得ることができるので、ボクシングを行う必要はありません。
ボクシングされたトレイトオブジェクトの利点は、具体的な型情報が消去された状態でトレイトオブジェクトの所有権を管理できるという点です。なので、逆に言えば所有権を管理する必要がなければボクシングは必要ありません。上に例示したコードのように借用で済むはずです。
ジェネリクス関数 - impl trait引数とトレイト境界の違い
元ネタ
回答
impl trait引数は、トレイト境界に脱糖(desugar)される。(ゆえにトレイト境界の糖衣構文である)
したがって以下は同じ。
trait Foo {}
fn func1(_: impl Foo) {}
fn func2<T: Foo>(_: T) {}
唯一の違いは、impl Trait引数は、その型を明白に指定させることができない。(ターボフィッシュが使えない)
trait Trait {}
fn foo<T: Trait>(t: T) {}
fn bar(t: impl Trait) {}
impl Trait for u32 {}
fn main() {
foo::<u32>(0); // this is allowed
bar::<u32>(0); // this is not
/*
error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
--> src/main.rs:10:11
|
10 | bar::<u32>(0); // this is not
| ^^^ explicit generic argument not allowed
*/
}
anyhowを使う
anyhow::Error
https://docs.rs/anyhow/1.0.44/anyhow/struct.Error.html
動的エラー型を包み込んだエラー型。
Box<dyn std::error::Error>との違い
- 静的な型
- もどり道(backtrace)を保証する
- narrow pointer
Display representations
Display 表し方
esprintln!("{}", err)
undering error(anyhow::Errorが包み込んだerror) もしくは、contextを表示する。
例: Failed to read instrs from ./path/to/instrs.json
esprintln!("{:#}", err)
anyhowのcausesのデフォルトフォーマットを出力する
例: Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)
esprintln!("{:?}", err)
キャプチャーしたバックトレースを出力する。
例: Error: Failed to read instrs from ./path/to/instrs.json
Caused by: No such file or directory (os error 2)
anyhow::Result<T>
実態
//anyhow type Result = Result<T, anyhow::Error>
fn mainでは返すことができない。
anyhow::Errorを伝搬してくれる。
anyhow::Context
Result<T, anyhow::Error>に変換するcontextメソッドを提供する。
std::core::Result<T, E>からの変換
let file = File::open("memo.txt").context(format!("unable to open memo.txt)")?;
Option<T>からの変換
let first = vec.first()?.context("unable to get first from vec")
anyhow! bail!
anyhow!マクロ
anyhow::Errorをかんたんに生成
例: stringからの生成
fn lookup(key: &str) -> Result<V> {
if key.len() != 16 {
return Err(anyhow!("key length must be 16 characters, got {:?}", key));
}
...
bail!マクロ
実態
return(anyhow!($args....))
これを使うことでかんたんにanyhow::Result<T>にして返すことができる
if !has_permission(user, resource) {
bail!("permission denied for accessing {}", resource);
}
thiserrorとの組み合わせ
#[derive(Error, Debug)]
enum ScienceError {
#[error("resouce limit exceeded")]
RecursionLimitExceeded,
...
}
if depth > MAX_DEPTH {
bail!(ScienceError::RecursionLimitExceeded);
}
anyhowメモ
anyhowのメリット
- エラーに説明を追加できる
- anyhow::Result<T>は、エラーの伝播ルートをスタックトレースとして出力してくれる
anyhowがない場合
Result<T, E> と std::error::Errorトレイトを使う。
fn count_words<R: Read>(input: &mut R) -> Result<u32, Box<dyn Error>> {
let reader = BufReader::new(input);
let mut wordcount = 0;
for line in reader.lines() { // line = Result<String>
for _word in line?.split_whitespace() {
wordcount += 1;
}
}
Ok(wordcount)
}
fn main() -> Result<(), Box<dyn Error>> {
// 複数ファイル対応
for filename in env::args().skip(1).collect::<Vec<String>>() {
// どのファイルが開けなかったのか出力されない
let mut reader = File::open(&filename)?;
let wordcount = count_words(&mut reader)?;
println!("{} {}", wordcount, filename);
}
Ok(())
}
anyhowがある場合
anyhow::Result<T> と anyhow::Error型を使う。
use anyhow::{Result, Context};
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
fn count_words<R: Read>(input: R) -> Result<u32> {
let reader = BufReader::new(input);
let mut wordcount = 0;
for line in reader.lines() {
for _word in line.context("not line")?.split_whitespace() {
wordcount += 1;
}
}
Ok(wordcount)
}
fn run() -> Result<()> {
for filename in env::args().skip(1).collect::<Vec<String>>() {
// contextで、anyhow::Errorに変換し、説明を付与できる。
let file = File::open(&filename).context(format!("unable to open {}", filename))?;
let wordcount = count_words(&file).context(format!("unable to count words in {}", filename))?;
println!("{} {}", wordcount, filename);
}
Ok(())
}
fn main() /*NG main関数ではResult<(), anyhow::Error>を返せない-> Result<()>*/ {
if let Err(error) = run() {
// Errorを出力する
eprintln!("Error: {:?}", error);
std::process::exit(0);
}
}
Borrow<Borrowed>とToOwnedトレイト
- Borrow<Borrowed>トレイト - Borrowed型を借用できることを表す
- ToOwnedトレイト - Borrow<Borrowed>型を実装すると自動実装される
use std::borrow::Borrow;
#[derive(Debug)]
struct ToolBox(Tool);
#[derive(Debug)]
struct Tool;
impl Borrow<Tool> for ToolBox {
fn borrow(&self) -> &Tool {
&self.0
}
}
let tool_box = ToolBox(Tool);
let tool = tool_box.borrow() as &Tool;
// ToOwnedが自動導出される
let tool_box = tool.to_owned();
dbg!(tool_box);
代表的なBorrow<Borrowed>型
- String = Borrow<str>
- Vec<T> = Borrow<[T]>
- Box<T> = Borrow<T>
- PathBuf = Borrow<Path>