match式による構造破壊マッチング
match式による構造破壊マッチング
match式のパターンマッチ内において、値を構造破壊することが出来る。
Destructuring Tuples
タプル値を構造破壊してパターンマッチ
let triple = (0, -2, 3); match triple { (0, y, z) => plintln!("First is `0`, `y` is {:?}, `z` is {:?}", y, z), (1, ..) => println!("First is `1`, and the rest doesn't matter"), _ => println!("It doesn't matter what they are"), }
Destructuring enums
enum値を構造破壊してパターンマッチ
#[allow(dead_code)] enum Color { Red, Blue, Green, RGB(u32, u32, u32), CMYK(u32, u32, u32, u32), } fn main() { let color = Color::RGB(122, 17, 40); match color { Color::Red => println!("The color is Red!"), Color::Blue => println!("The color is Blue!"), Color::Green => println!("The color is Green!"), Color::RGB(r, g, b) => println!("Red: {}, Green: {}, Blue: {}", r, g, b), Color::CMYK(c, m, y, k) => println!("Cyan: {}, Magenta: {}, Yellow: {}, key (Black): {}!", c, m, y, k), } }
Destructurring pointer
パターンマッチによるデリファレンスが出来る
fn main() { let x = 4; let reference = &x; match reference { // シングル参照とマッチング、=>右辺は、デリファレンスされる &val => println!("Got a value via destructuring: {:?}", val), } }
ダブル参照もデリファレンス可能
fn main() { let x = 4; let reference = &&x; match reference { // ダブル参照をマッチング、=>右辺は、デリファレンスされる &&val => println!("Got a value via destructuring: {:?}", val), } }
matchの前に、*
演算子によるdereferenceも化
match *reference { val => println!("Got a value via derefereencing: {:?}", val), }
ref
キーワードを付けることで、右側が&
演算子がついてなくても参照として受け取ることが出来る
// refキーワード let ref reference = 3; // &i32 match *reference { val => println!("Got a value via derefereencing: {:?}", val), }
参照型として値を受け取る
fn main() { let value = 5; match value { // rは参照型として受け取れる ref r => println!("Got a reference to a value: {:?}", r), } }
ref mut
によって、match式内部で値を変更できる
let mut mut_value = value; match mut_value { ref mut m => { *m += 10; }, } println!("We added 10. `mut_value`: {:?}", mut_value);
Destructuring struct
match式によって構造体の解体も可能
fn main() { struct Foo { x: (u32, u32), y: u32, } let foo = Foo { x: (1, 2), y: 3 }; match foo { Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y), // 順番は関係しない Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i), // いくつかの変数を無視できる Foo { y, .. } => println!("y = {}, we don't care about x", y), } }
参照
https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring.html
型の種類とプリミティブデータ型
型の種類
T
: 通常型&T
: 参照型 - ある所有された値への借用を表す型[T; N]
: 配列型[T]
: スライス型*const T
: ポインタ型(T, U, V)
: タプル型fn(T) -> V
: 関数型
プリミティブデータ型
bool
char
let x = 'x'; let two_heart = '💕';
Numeric types
i8
i16
i32
i64
u8
u16
u32
isize
: ポインタ型のサイズ,32bitCPUならば4バイト、64bitCPUならば8バイトusize
: indexに指定出来る値f32
f64
Arrays [T; N]
配列型
let a = [1, 2, 3]; // a: [i32; 3]; let mut m = [1, 2, 3]; // m: [i32; 3];
要素の確保と0埋め
let a = [0; 20]; // a[i32; 20]; println!("{}", a.len());
Slices
スライス型
[T]
/ &mut [T]
配列の一部へコピーを発生させないで、安全に効率的なアクセスをする。 動的Sized型であるため、参照型(値の借用)である必要がある。
let a = [1, 2, 3, 4] // 配列からスライスを生成する let complete = &a[..]; let middle = &a[1..4];
str
文字列型の最もプリミティブな型を表す。
Tuples
タプルは、固定長の順序つきリスト
let x = (1, "hello") // x: (i32, &str)
destructuring let
let (x, y, z) = (1, 2, 3); dbg!(x);
タプルインデクシング
let tuple = (1, 2, 3); let x = tuple.0; let y = tuple.1; let z = tuple.2;
Functions
関数も型。 (クロージャーはランタイムで自動生成される)
fn foo(x: i32) -> i32 { x } fn main() { let x: fn(i32) -> i32 = foo; }
参照
型 作成と実装
型(Type)の作成と実装
Rustは型(Type)の作成と実装が分かれる。
(最近はトレイトも実はサイズを持たない型(Type)と言えるのではないかと思うようになった)
型の作成は、struct
, enum
を使い、実装は impl
を使う。
型の作成 struct
enum
Struct
struct Point { x: i32, y: i32, z: i32, }
Enum
enum IpAddr { V4(u8, u8, u8, u8), V6(String), }
Trait
trait Human { fn get_name() -> String; fn get_age() -> i32; }
トレイトは振る舞いを定義できる型(Type)
型の実装 impl
impl
型にメソッドを追加する
impl Point { fn add(&self, other: Point) -> Point { Point(self.x + other.x, self.y + other.y, self.z + other.z) } }
impl Ipaddress { fn equal(&self, other) -> Bool { } }
参照型とポインタ型
参照型とポインタ型
- 参照型
&T
/&mut T
- ある所有された値への借用を表す型 - ポインタ型
*const T
/*mut T
-null
を表すことができる
ポインタ型を使うメリットとデメリット
Rustではポインタ型は、値がnullの可能性がある。 したがって扱う場合は、unsafeブロック内部で行う必要がある。 その代わり、Cポインタのように配列の走査が行える。
ポインタ型
変数宣言時に、初期化せずに、nullを代入することが出来る。 ただし、参照外し(*演算子を使って)がされた場合は、非nullで、かつ初期化された値を返す必要がある。
fn main() { let p: *const u8; unsafe { p = "ABC".as_ptr(); println!("{:?}", *p as char); // u8からcharにキャスト println!("{:?}", *p.add(1) as char); println!("{:?}", *p.add(2) as char); } }
*p
: 参照外しに寄ってu8
に変換
ポインタ型の作り方
1. 参照強制(&T or &mut T)による生成
let my_num: i32 = 10; let ptr_my_num: *const i32 = &my_num; // アノテーションを付ける必要がある let mut my_speed: i32 = 88; let ptr_my_speed: *mut i32 = &mut my_speed;
NOTE:
2. Box型から値を取り出して生成
let my_num: Box<i32> = Box::new(10); let p_my_num: *const i32 = &*my_num;
NOTE:
- Box型はスマートポインタなので、
*演算子(参照外し演算子)
によって内部の値を取り出せる - &演算子によってポインタ型に変換している
3. Cから取得する
extern crate libc; use std::mem; unsafe { let my_num: *mut i32 = libc::malloc(mem::size_of::<i32>()) as *mut i32; if my_num.is_null() { panic!("failed to allocate memory"); } libc::free(my_num as *mut libc::c_void); }
一般的に、 Rustからmalloc,freeを直接使わない。 しかし、C APIはたくさんのポインタを手渡しするため、Rustにおいてもポインタとして扱う。
ポインタ型の使い方
配列走査などをポインタ型で行うと、処理速度の向上が見込めるが、nullもしくは未初期化やオーバーフローしている可能性があるため、unsafeブロック内部で処理を記述する必要がある。
値の取得
fn main() { let s: &str = "ABC"; let ptr: *const u8 = s.as_ptr(); unsafe { println!("{}", *ptr); // 先頭ポインタ(u8)の値を指し示す。型はu8。 println!("{}", *ptr.offset(0) as char); // A println!("{}", *ptr.offset(1) as char); // B } }
*
演算子- ポインタが指し示している先頭の値を指し示す
- unsafeブロック内部で使用する必要がある
offset(self, count: isize)
- ポインタからのオフセットを計算する
- countの単位は、
size_of::<T>()
バイト毎になる - コンビニエンスメソッドとして、
add(count: usize)
,sub(count: usize)
が存在する
let s: &str = "ABC"; unsafe { let end: *const u8 = s.as_ptr().add(3); println!("{}", *end as char); // LF(0xA)を指し示している println!("{}", *end.sub(1) as char); // C println!("{}", *end.sub(2) as char); // B }
- addに指定するcountによっては、オーバーフローしている可能性がある
値の走査
文字の検索
fn main() { let text = "A language empowering everyone to build reliable and efficient software."; let word = "everyone"; let p_text: *const u8 = text.as_ptr(); let p_word: *const u8 = word.as_ptr(); let mut exist = false; 'i: for i in 0..text.len() { 'j: for j in 0..word.len() { unsafe { if *p_text.add(i + j) != *p_word.add(j) { break 'j; } } if j == word.len() - 1 { exist = true; break 'i; } } } println!("result = {}", exist); }
関連
スマートポインタ https://yossan.hatenablog.com/entry/2020/09/20/224329
Dynamically Sized Type (DST) https://yossan.hatenablog.com/entry/2020/10/11/230422
参照
Primitive Type pointer https://doc.rust-lang.org/std/primitive.pointer.html
Iterator::collect()
Iterator::collect()
fn collect<B>(self) -> B where B: FromIterator<Self::Item>,
IteratorをCollectionに変換する。
collect()
で変換できるCollectionはFromIterator<Self::Item>
トレイトが実装されている必要がある。
Trait std::iter::FromIterator
pub trait FromIterator<A> { fn from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = A>; }
Iteratorから型を生成できることを表す。
FromIterator::from_iter()
メソッドを直接呼ぶことはなく、Iterator::collect()
メソッドを通して使用する。
反対のトレイトとして、IntoIterator
がある。
Vec<_> への変換
impl<T> FromIterator<T> for Vec<T>
let five_fives = std::iter::repeat(5).take(5); let v: Vec<i32> = five_fives.collect();
[char]からStringの生成
impl FromIterator<char> for String
let chars = ['h', 'e', 'l', 'l', 'o']; let text = chars.iter().collect::<String>();
単精度浮動小数点数への変換
123
2進数に変換
0b1111011
単精度浮動小数点数形式に正規化
0b1.111011 x 2^(6)
単精度浮動小数点数データ構造に変換
- 符号部: 0
- 指数部: 6 + 127 = 134
- 仮引数部: 11101100000000000000000
Swiftによる確認
let f = 123 as Float print("符号: ", f.sign) print("指数: ", f.exponent) print("指数部: ", f.exponentBitPattern) print("仮引数部: ", String(f.significandBitPattern, radix:2)); /* 符号: plus 指数: 6 指数部: 133 仮引数部: 11101100000000000000000 */
123.625
2進数に変換する
0b1111011.101
単精度浮動小数点数形式に正規化
0b1.111011101 x 2(^6)
単精度浮動小数点数データ構造に変換
- 符号部: 0
- 指数部: 6 + 127 = 134
- 仮引数部: 111011101
Swiftによる確認
let f = 123.625 as Float print("符号: ", f.sign) print("指数: ", f.exponent) print("指数部: ", f.exponentBitPattern) print("仮引数部: ", String(f.significandBitPattern, radix:2)); /* 符号: plus 指数: 6 指数部: 133 仮引数部: 11101110100000000000000 */
123_456_789
仮引数部が23桁を超えるので、丸め誤差が発生する
2進数に変換
0b111010110111100110100010101
単精度浮動小数点数形式に正規化
0b1.11010110111100110100010(101) x 2^(26)
単精度浮動小数点数データ構造に変換
- 符号部: 0
- 指数部: 26 + 127 = 153
- 仮引数部: 11010110111100110100011 // 桁上り
Swiftによる確認
let f = 123_456_789 as Float print("符号: ", f.sign) print("指数: ", f.exponent) print("指数部: ", f.exponentBitPattern) print("仮引数部: ", String(f.significandBitPattern, radix:2)); /* 符号: plus 指数: 26 指数部: 153 仮引数部: 11010110111100110100011 */
0.1
仮引数部が23桁を超えるので、丸め誤差が発生する
2進数に変換
0b0.0001100110011001101
単精度浮動小数点数形式に正規化
0b1.100110011001101 x 2^(-4)
単精度浮動小数点数データ構造に変換
- 符号部: 0
- 指数部: -4 + 127 = 123
- 仮引数部: 100110011001101
Swiftによる確認
let f = 0.1 as Float print("符号: ", f.sign) print("指数: ", f.exponent) print("指数部: ", f.exponentBitPattern) print("仮引数部: ", String(f.significandBitPattern, radix:2)); /* 符号: plus 指数: -4 指数部: 123 仮引数部: 10011001100110011001101 */
単精度浮動小数点数(IEEE754) の作り方
書いたこと
単精度浮動小数点数形式とデータ構造
2進数をそのままでは単精度浮動小数点数データ構造に変換することはできない。 一旦、単精度浮動小数点数形式に正規化を行う必要がある。
単精度浮動小数点数形式と正規化
浮動小数点数データ構造に変換する前段階として、2進数を以下のように最上位ビットを1にして、位取りを2の指数で表すようにする。
この形式に変換することを正規化という。
例 3.1415の正規化
正規化方法
1. 3.1415を2進数に変換する
0b11.00100100001110010101100
2. 最上位ビットが1になるように位を調整する
0b1.10010010000111001010110 x 2^1
単精度浮動小数点数データ構造への変換
正規化した2進数とIEEE754で定義された単精度浮動小数点数データ構造は以下の関係を持つ。
符号部
1ビットで構成され、正規化した際に正数のときは0、負数のときは1。
指数部
8ビットで構成され、指数部の値は、正規化した際の指数値の値に+ 7F(127)
足した値となる。
これによって0 ~ 7Eまでの値に負数(-127 ~ -1)を割当て、80 ~ FFまでの値に正数(1 ~ 127)を割りてている。
仮引数部
23ビットで構成され、正規化した値に対して小数点以下上位23ビット取得する。
例 3.1415
2進数に変換
0b11.00100100001110010101100
正規化した値
0b1.100100100001110010101100
正規化した値をデータ構造に変換
正規化した値 | データ構造に割当てられる値 |
---|---|
符号: 正数 | 符号部: 0 |
指数: 1 | 指数部: 128 |
小数点以下上位24桁 | 仮引数部 |
100100100001110010101100 | 10010010000111001010110 |
2. 精度と誤差
単精度浮動小数点数は、位取りを整えることが出来るため、値の大小に関わらず常に精度は2進数で小数点以下23桁が保証される
例: 円周率が3.1415として、直径が250と0.25における精度を10進数で考える。(1の位を最上位に正規化)
有効桁数 | 250 | 0.25 |
---|---|---|
小数点以下4桁 | 7.8535 x 102 | 7.8375 x 10^-1 |
小数点以下3桁 | 7.854 x 102 | 7.854 x 10^-1 |
小数点以下2桁 | 7.85 x 102 | 7.85 x 10^-1 |
小数点以下1桁 | 7.9 x 102 | 7.9 x 10^-1 |
値の大小によって精度は変わらないが、有効桁数によって、本来の値と誤差が発生している。
代表的な誤差
- 丸め誤差: 23桁をオーバーフローした時
- 情報落ち: 巨大な数字と小さな数字を足した時
- (有効数字の)桁落ち: 極めて近い値同士、引き算した時
丸め誤差 (打切り誤差)
単精度浮動小数点数に変換した際に、小数点数以下23桁を超える範囲は、24桁目で丸められる。 (Swiftの場合、1ビットの数が偶数の場合は、桁上りし、奇数の場合、切り捨てられていると思われる)
例1: 16777215 (0b1.1111111111111111111111) より大きい数
16777215より大きい数は2進数に変換し、正規化すると24桁を超える
例2: 0.1
0.75は0.5(1/2)と0.25(1/4)の和で表せるが、0.1は表せないため、2進数に変換すると無限小数となり、23桁で打ち切られる。
0.1を2進数に変換
0b0.000110011001100110011001100110
浮動小数点数形式に正規化
0b1.10011001100110011001101 \* 2^(-4)
0b1.10011001100110011001101 = 1.6000000238418579
1.60000002384185791016 \* 2^(-4) = 0.10000000149
したがって、0.1は単精度浮動小数点数では、0.10000000149 となる。
例: Swiftでの確認 0.1を10回足しても1.0にならない
var f = 0.1 as Float for _ in 0..<10 { f += 0.1 } print(f == 1.0) // false
情報落ち
浮動小数点数の和を計算する際、指数部がそろえられるため、巨大な数字との和をとると、小さい方の値が落ちてしまう。
例: Swiftによる確認 1千万と0.1との和
var f = 10_000_000 as Float for _ in 0..<1000 { f += 0.1 } print(f == 10_000_000) // true
桁落ち
極めて近い値同士で引き算をすると、有効数字が小さくなり、0埋めされることによって誤差が発生する。
例: sqrt(113) - sqrt(112)
それぞれ2進数で表すと
sqrt(113) = 0b1010.10100001010100010100 sqrt(112) = 0b1010.10010101001111111101
浮動小数点数形式へ正規化
sqrt(113) = 0b1.01010100001010100010100 x 2^(3) sqrt(112) = 0b1.01010010101001111111101 x 2^(3)
したがって
sqrt(113) - sqrt(112) = 0b0.00000001100000100010111 x 2^(3)
正規化すると0埋めが発生する
sqrt(113) - sqrt(112) = 0b1.10000010001011100000000 x 2^(-5)