<style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.6; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 4.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 2.4vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h2 { padding: 0 1.5vw; margin: 0.0vw 0 2.0vw -2.0vw; border-left: solid 1.2vw #2980b9; border-bottom: solid 0.8vw #d7d7d7; } </style> # 第19回  ## 第19章 高度な機能 ### 2021/01/08 原山和之 --- ## 本日やること - Unsafe Rust: Rustの保証の一部を抜けてその保証を手動で保持する責任を負う方法 - 高度なライフタイム: 複雑なライフタイム状況の記法 - 高度なトレイト: 関連型、デフォルト型引数、フルパス記法、スーパートレイト、トレイトに関連するニュータイプパターン - 高度な型: ニュータイプパターンについてもっと、型エイリアス、never型、動的サイズ型 - 高度な関数とクロージャ: 関数ポインタとクロージャの返却 <!-- この章で使用することを学ぶ機能は、かなり限定的な場面でしか役に立ちません。あまり頻繁には手を伸ばすことがない可能性はありますが、 Rustが提供しなければならない機能全ての概要を確かに把握してもらいたいのです。 --> --- ## Unsafe Rust - メモリ安全保証を強制しない、強大な力を与えてくれます。 - unsafeコードを誤って使用したら、 nullポインタ参照外しなどのメモリ非安全に起因する問題が起こる。 --- ## unsafeの強大な力(superpower) - unsafeキーワード - 生ポインタを参照外しすること - unsafeな関数やメソッドを呼ぶこと - 可変で静的な変数にアクセスしたり変更すること - unsafeなトレイトを実装すること --- ## 生ポインタを参照外しする - 参照やスマートポインタと異なり、生ポインタは: - 同じ場所への不変と可変なポインタや複数の可変なポインタが存在することで借用規則を無視できる - 有効なメモリを指しているとは保証されない - nullの可能性がある - 自動的な片付けは実装されていない <!-- ポインタを他の言語との間で受け渡したり、所有権システムの管理から外したいとき 保証された安全性を諦めてパフォーマンスを向上させたり、 Rustの保証が適用されない他の言語やハードウェアとのインターフェイスの能力を得ることができます。--> --- ## 生ポインタを参照外しする(例) ```rust= let mut num = 5; let r1 = &num as *const i32; // 不変 let r2 = &mut num as *mut i32; // 可変 ``` <!-- 参照から不変と可変な生ポインタを生成する方法、 ポインタも不変や可変になり得て、それぞれ*const Tと*mut Tと表記されます。 safeコードで生ポインタを生成できます。FFIとかで使うみたい。--> --- ## unsafeブロックを使用して生ポインタを参照外しする ```rust= let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32; unsafe { println!("r1 is: {}", *r1); println!("r2 is: {}", *r2); } ``` <!-- unsafeブロックの外では、生ポインタを参照外しできないだけなのです。 実際に使用するときは、unsafeブロックが必要になる参照外し演算子の*を生ポインタに使っています。 unsafeの意味として、Rustの所有権規則により、不変参照と可変参照を同時に存在させられないからです。生ポインタなら、 同じ場所への可変なポインタと不変なポインタを生成でき、可変なポインタを通してデータを変更し、データ競合を引き起こす可能性があります。 --> --- ## unsafeな関数やメソッドを呼ぶ こちらは、本体で何もしないdangerousというunsafeな関数です: ```rust= unsafe fn dangerous() {} unsafe { dangerous(); } ``` <!-- unsafeな関数やメソッドも見た目は、 普通の関数やメソッドと全く同じですが、残りの定義の前に追加のunsafeがあります。 --> --- ## unsafeコードに安全な抽象を行う 例えばこんな処理: ```rust= let mut v = vec![1, 2, 3, 4, 5, 6]; let r = &mut v[..]; let (a, b) = r.split_at_mut(3); assert_eq!(a, &mut [1, 2, 3]); assert_eq!(b, &mut [4, 5, 6]); ``` <!-- 安全な関数でunsafeなコードをラップすることは一般的な抽象化です。 この安全なメソッドは、可変なスライスに定義されています: スライスを1つ取り、引数で与えられた添え字でスライスを分割して2つにします。--> --- ## 似た関数を作成してみる - エラー ```rust= fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = slice.len(); assert!(mid <= len); (&mut slice[..mid], &mut slice[mid..]) } ``` <!-- 同じスライスから2回借用していることだけ知っています。2つのスライスが被らないので、 スライスの異なる部分を借用することは、根本的に大丈夫なのですが、コンパイラはこれを知れるほど賢くありません。 --> --- ## 動くように修正 ```rust= use std::slice; fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = slice.len(); let ptr = slice.as_mut_ptr(); //Returns an unsafe mutable pointer to the slice's buffer. assert!(mid <= len); unsafe { (slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid)) } } ``` - [slice.as_mut_ptr()](https://doc.rust-lang.org/std/primitive.slice.html#method.as_mut_ptr) - [from_raw_parts_mut()](https://doc.rust-lang.org/std/slice/fn.from_raw_parts_mut.html) --- ## 安全じゃないけどできる ```rust= use std::slice; let address = 0x012345usize; let r = address as *mut i32; let slice = unsafe { slice::from_raw_parts_mut(r, 10000) }; ``` <!-- 任意のメモリアドレスからスライスを生成する。 そのアドレスにデータがある可能性もあるし、ない可能性もあり、 コンパイラがコードを最適化してメモリアクセスがなくなる可能性もあるし、プログラムがセグメンテーションフォールトでエラーになる可能性もあります。 --> --- ## extern関数を使用して、外部のコードを呼び出す ```rust= extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { // -3の絶対値は、Cによると{} println!("Absolute value of -3 according to C: {}", abs(-3)); } } ``` <!-- externブロック内で宣言された関数は、常にRustコードから呼ぶにはunsafeになります。理由は、 他の言語では、Rustの規則や保証が強制されず、コンパイラもチェックできないので、 安全性を保証する責任はプログラマに降りかかるのです。 --> --- ## 可変で静的な変数にアクセスしたり、変更する - グローバル変数は、static(静的)変数 ```rust= static HELLO_WORLD: &str = "Hello, world!"; fn main() { // 名前は: {} println!("name is: {}", HELLO_WORLD); } ``` <!-- グローバル変数をRustは確かにサポートしていますが、 Rustの所有権規則で問題になることもあります。2つのスレッドが同じ可変なグローバル変数にアクセスしていたら、 データ競合を起こすこともあります。 --> --- ## 定数と静的変数の違い - COUNTERという可変で静的な変数を宣言し、アクセスし、変更する方法を表示しています。 ```rust= static mut COUNTER: u32 = 0; fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_count(3); unsafe { println!("COUNTER: {}", COUNTER); } } ``` <!-- mutキーワードを使用して可変性を指定します。COUNTERを読み書きするコードはどれも、unsafeブロックになければなりません。 --> --- ## unsafeなトレイトを実装する ```rust= unsafe trait Foo { // methods go here // メソッドがここに来る } unsafe impl Foo for i32 { // method implementations go here // メソッドの実装がここに来る } ``` --- ## いつunsafeコードを使用するべきか unsafeを使って議論したばかりの4つの行動(強大な力)のうちの1つを行うのは間違っていたり、認められさえもしないものではありません。 ですが、unsafeコードを正しくするのは、より巧妙なことでしょう。コンパイラがメモリ安全性を保持する手助けをできないからです。 unsafeコードを使用する理由があるなら、そうすることができ、明示的にunsafe注釈をすることで問題が起きたら、 その原因を追求するのが容易になります。 --- ## 高度なライフタイム - ライフタイム・サブタイピング: あるライフタイムが他のライフタイムより長生きすることを保証する - ライフタイム境界: ジェネリックな型への参照のライフタイムを指定する - トレイトオブジェクトのライフタイムの推論: コンパイラにトレイトオブジェクトのライフタイムを推論させることと指定する必要があるタイミング --- ## サブタイム サブタイピング - ライフタイム・サブタイピング(lifetime subtyping; 訳注: あえて訳すなら、ライフタイムの継承) エラーな例: コンパイラに怒られます。 ```rust= struct Context(&str); struct Parser { context: &Context, } impl Parser { fn parse(&self) -> Result<(), &str> { Err(&self.context.0[1..]) } } ``` <!-- コンパイラはContextの文字列スライスとParserのContextへの参照にライフタイム引数を期待するので、 このコードをコンパイルすると、エラーに --> --- ## ライフタイム引数で修正してみる - 単純にコンパイルは通る ```rust= struct Context<'a>(&'a str); struct Parser<'a> { context: &'a Context<'a>, } impl<'a> Parser<'a> { fn parse(&self) -> Result<(), &str> { Err(&self.context.0[1..]) } } ``` <!-- コンパイラにParserはライフタイム'aのContextへの参照を保持し、 ContextはParserのContextへの参照と同じ期間生きる文字列スライスを保持していると指示しています。 --> --- ## 実際に使用してみる - 問題がありエラーになる。 ```rust= fn parse_context(context: Context) -> Result<(), &str> { Parser { context: &context }.parse() } ``` ```rust= fn parse<'a>(&'a self) -> Result<(), &'a str> { ``` --- ## 修正 ```rust= struct Context<'s>(&'s str); struct Parser<'c, 's: 'c> { context: &'c Context<'s>, } impl<'c, 's> Parser<'c, 's> { fn parse(&self) -> Result<(), &'s str> { Err(&self.context.0[1..]) } } fn parse_context(context: Context) -> Result<(), &str> { Parser { context: &context }.parse() } ``` --- ## ジェネリックな型への参照に対するライフタイム境界 - エラー ```rust= struct Ref<'a, T>(&'a T); ``` - 修正 ```rust= struct Ref<'a, T: 'a>(&'a T); ``` --- ## トレイトオブジェクトライフタイムの推論 ```rust= trait Red { } struct Ball<'a> { diameter: &'a i32, } impl<'a> Red for Ball<'a> { } fn main() { let num = 5; let obj = Box::new(Ball { diameter: &num }) as Box<Red>; } ``` <!-- トレイトオブジェクトのデフォルトのライフタイムは、'static。 &'a Traitや&'a mut Traitに関して、トレイトオブジェクトのデフォルトのライフタイムは、'a。 単独のT: 'a節について、トレイトオブジェクトのデフォルトのライフタイムは、'a。 複数のT: 'aのような節について、デフォルトのライフタイムはない; 明示しなければならない。 --> --- ## 関連型でトレイト定義においてプレースホルダーの型を指定する - 関連型ItemがあるIteratorトレイトの定義 ```rust= pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; } ``` <!-- 型Itemはプレースホルダー型でnextメソッドの定義は、型Option<Self::Item>の値を返すことを示しています。 Iteratorトレイトを実装するものは、Itemの具体的な型を指定し、nextメソッドは、 その具体的な型の値を含むOptionを返します。 --> --- ## 13章の復習 ```rust= impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { // --snip-- ``` - なぜこうしないのか。。。 ```rust= pub trait Iterator<T> { fn next(&mut self) -> Option<T>; } ``` <!-- ジェネリクスを使用すると、各実装で型を注釈しなければならないことです; Iterator<String> for Counterや他のどんな型にも実装することができる。 CounterのIteratorの実装が複数できる。トレイトにジェネリックな引数があると、 毎回ジェネリックな型引数の具体的な型を変更してある型に対して複数回実装できる --> --- ## Serverトレイトを考えてみる - Server振る舞いに定義してみる。 ```rust= use std::str::FromStr; trait Server { // type 型名で関連型を宣言 type Response; // トレイト強化意味設定できる type Request: FromStr; fn handle(&self, req: Self::Request) -> Self::Response; } ``` --- ## デフォルトのジェネリック型引数と演算子オーバーロード - 例 ```rust= use std::ops::Add; #[derive(Debug, PartialEq)] struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } fn main() { assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 }, Point { x: 3, y: 3 }); } ``` <!-- Rustでは、独自の演算子を作ったり、任意の演算子をオーバーロードすることはできません。しかし、 演算子に紐づいたトレイトを実装することでstd::opsに列挙された処理と対応するトレイトをオーバーロードできます。 例えば、リスト19-22で+演算子をオーバーロードして2つのPointインスタンスを足し合わせています。 これは拡張するときなどに使えますね。 --> --- ## Addトレイト内の定義 ```rust= trait Add<RHS=Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; } ``` <!-- デフォルト型引数と呼ばれています。 Addトレイトを実装する際にRHSの具体的な型を指定しなければ、RHSの型は標準でSelfになる --> --- ## トレイトの例 ```rust= use std::ops::Add; struct Millimeters(u32); struct Meters(u32); impl Add<Meters> for Millimeters { type Output = Millimeters; fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) } } ``` <!-- MillimetersをMetersに足すため、Selfという既定を使う代わりにimpl Add<Meters>を指定して、 RHS型引数の値をセットしています。 --> --- ## デフォルト型引数を使用方法 - 既存のコードを破壊せずに型を拡張する - ほとんどのユーザは必要としない特定の場合でカスタマイズを可能にする --- ## 同じ名前のメソッドを呼ぶ 例: ```rust= trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { // キャプテンのお言葉 println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { // 上がれ! println!("Up!"); } } impl Human { fn fly(&self) { // *激しく腕を振る* println!("*waving arms furiously*"); } } ``` <!-- Humanに実装されつつ、 flyメソッドはHumanに直接にも実装されている。--> --- ## 呼んでみる ```rust= fn main() { let person = Human; person.fly(); } ``` ``` *waving arms furiously* ``` <!-- Humanのflyが呼ばれる --> --- ## 明示的な記法を使用して呼ぶには ```rust= fn main() { let person = Human; Pilot::fly(&person); Wizard::fly(&person); person.fly(); } ``` --- ## 関連関数 ```rust= trait Animal { fn baby_name() -> String; } struct Dog; impl Dog { fn baby_name() -> String { // スポット(Wikipediaによると、飼い主の事故死後もその人の帰りを待つ忠犬の名前の模様) String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { // 子犬 String::from("puppy") } } fn main() { // 赤ちゃん犬は{}と呼ばれる println!("A baby dog is called a {}", Dog::baby_name()); } ``` <!-- Animalトレイトには、関連関数baby_name、構造体DogのAnimalの実装、 Dogに直接定義された関連関数baby_name --> --- ## 前の結果 ``` A baby dog is called a Spot ``` --- ## 例えば、、、 ```rust= fn main() { println!("A baby dog is called a {}", Animal::baby_name()); } ``` <!-- puppyの方を呼びたいけど、これだとエラー --> --- ## 修正します。 ```rust= fn main() { println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); } ``` --- ## スーパートレイトを使用して別のトレイト内で、あるトレイトの機能を必要とする 例: これを作りたい ``` ********** * * * (1, 3) * * * ********** ``` --- ## 大枠の定義 - アウトラインのトレイト作成 ```rust= use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {} *", output); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } } ``` --- ## OutlinePrintを実装 ```rust= struct Point { x: i32, y: i32, } impl OutlinePrint for Point {} ``` <!-- エラーになります。 --> --- ## 修正 ```rust= use std::fmt; impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } ``` <!-- そうすれば、PointにOutlinePrintトレイトを実装してもコンパイルは成功し、 Pointインスタンスに対してoutline_printを呼び出し、アスタリスクのふちの中に表示することができます。 --> --- ## ニュータイプパターンを使用して外部の型に外部のトレイトを実装する ```rust= use std::fmt; struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {}", w); } ``` <!-- 外部のトレイトを外部の型に対して実装することはできません。とゆうルールがありました。 DisplayトレイトもVec<T>型もクレートの外で定義されているので、 直接それを行うことはオーファンルールにより妨げられます。ニュータイプパターンがよくわかってない。--> --- ## 型安全性と抽象化を求めてニュータイプパターンを使用する --- ## 型エイリアスで型同義語を生成する ```rust= type Kilometers = i32; let x: i32 = 5; let y: Kilometers = 5; println!("x + y = {}", x + y); ``` <!-- Kilometersとi32が同じ型なので、両方の型の値を足し合わせたり、Kilometersの値をi32引数を取る関数に渡せたりします。 --> --- ## never型は絶対に返らない - Rustには、!という名前の特別な型があります。 ```rust= fn bar() -> ! { // --snip-- } ``` <!-- 私たちは、 関数が値を返すことが決して (never) ない時に戻り値の型を記す場所に使われるので、never type(訳注: 日本語にはできないので、never型と呼ぶしかないか)と呼ぶのが好きです。 --> --- ## neverはプログラム終了 ```rust= let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; ``` ```rust= impl<T> Option<T> { pub fn unwrap(self) -> T { match self { Some(val) => val, None => panic!("called `Option::unwrap()` on a `None` value"), } } } ``` --- ## 動的サイズ付け型とSizedトレイト - **できない** ```rust= // こんにちは let s1: str = "Hello there!"; // 調子はどう? let s2: str = "How's it going?"; ``` <!-- コンパイラは、特定の型のどんな値に対しても確保するメモリ量を知る必要があり、ある型の値は全て同じ量のメモリを使用しなければなりません。 Rustでこのコードを書くことが許容されたら、これら2つのstr値は、同じ量のスペースを消費する必要があったでしょう。 ですが、長さが異なります: s1は、12バイトのストレージが必要で、s2は15バイトです。このため、 動的サイズ付け型を保持する変数を生成することはできないのです。 --> --- ## Sizedトレイト使う ```rust= fn generic<T>(t: T) { // --snip-- } fn generic<T: Sized>(t: T) { // --snip-- } ``` ```rust= fn generic<T: ?Sized>(t: &T) { // --snip-- } ``` <!-- ?Sizedのトレイト境界は、Sizedのトレイト境界の逆になります: これを「TはSizedかもしれないし、違うかもしれない」と解読する --> --- ## 関数ポインタ ```rust= fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) } fn main() { let answer = do_twice(add_one, 5); // 答えは{} println!("The answer is: {}", answer); } ``` <!-- fn型を使用して引数として関数ポインタを受け入れる do_twiceの引数fは、型i32の1つの引数を取り、 i32を返すfnと指定, The answer is: 12と出力, do_twiceの本体でfを呼び出すこと --> --- ## こういうこと ```rust= fn double(n: i32) -> { n + n } let mut f: fn(i32) -> i32 = double; ``` --- ## クロージャを返却する ```rust= fn returns_closure() -> Box<Fn(i32) -> i32> { Box::new(|x| x + 1) } ```
{"metaMigratedAt":"2023-06-15T18:02:00.837Z","metaMigratedFrom":"YAML","title":"第19回 第19章 高度な機能","breaks":true,"description":"Rust","slideOptions":"{\"theme\":\"white\",\"slideNumber\":\"c/t\",\"center\":false,\"transition\":\"none\",\"keyboard\":true}","contributors":"[{\"id\":\"ed5d0581-544f-4aa0-a6ad-2f48be3d325d\",\"add\":17584,\"del\":685}]"}
    595 views