Open9
rust トレイト
トレイトを実装
トレイトとは、別の言語では interface と表されることが多い
trait Pay {
fn pay(self, price: i32) -> Self;
}
関数の引数と戻り値の型が定義されている
このトレイトを struct に実装する
struct Money {
amount: i32,
}
trait Pay {
fn pay(self, price: i32) -> Self;
}
impl Pay for Money {
fn pay(self, price: i32) -> Self {
Money {
amount: self.amount - price,
}
}
}
typescriptだとimplements
トレイトを用意しなくても、struct に関数果たせる
struct Dollar {
amount: i32,
}
impl Dollar {
fn draw(self, val: i32) -> Self {
Dollar {
amount: self.amount - val,
}
}
}
ジェネリクス
struct Money<T> {
amount: T,
}
let money: Money<i32> = Money { amount: 10000 };
// or
let money: Money<i64> = Money::<i64> { amount: 10000 };
何も書かないと、型推論してくれる
ジェネリックなメソッド
struct Money<T> {
amount: T,
}
impl<T> Money<T> {
fn refer(self) -> Self {
Money {
amount: self.amount,
}
}
}
impl<T> と書くと、ジェネリックな形になる
let money: Money<i32> = Money { amount: 10000 };
let amount: Money<i32> = money.refer();
特定の方のみに実装する
impl Money<i32> {
fn pay(self, val: i32) -> Self {
Money {
amount: self.amount - val,
}
}
}
// OK
let money: Money<i32> = Money { amount: 10000 };
let amount: Money<i32> = money.pay(1000);
// NG
let money = Money::<i64> { amount: 10000 };
// i64 には pay method が生えていない
ちなみに、上記のようにインスタンス化しなくても
let money = Money::<i32> { amount: 10000 };
let money = Money::pay(money, 1000);
のように使うこともできる
.
で繋ぐような書き方は pay が self をとっているからできる
例えば、
impl ToString for str {
#[inline]
fn to_string(&self) -> String {
String::from(self)
}
}
"some_str".to_string()
From トレイト
前段はこのくらいにして、rust にはトレイトがそれはもうたくさん用意されている
Fromトレイトもそのうちの一つ
pub trait From<T>: Sized {
/// Converts to this type from the input type.
#[rustc_diagnostic_item = "from_fn"]
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn from(value: T) -> Self;
}
impl From<i32> for Money<i32> {
fn from(s: i32) -> Self {
Money { amount: s }
}
}
let money = Money::from(10);
上は Money<i32> に i32 を引数にとって、 Money<i32> を返す関数 from を実装している
当然 引数が f32 だとエラー
Money<i32> に f32 を引数にとって、 Money<i32> を返す関数 from は
impl From<f32> for Money<i32> {
fn from(s: f32) -> Self {
Money { amount: s as i32 }
}
}
let money = Money::from(10.0);
複数定義すると、コンパイラがわからなくなるので、指定が必要(kawaii)
impl From<i32> for Money<i32> {
fn from(s: i32) -> Self {
Money { amount: s }
}
}
impl From<i32> for Money<f32> {
fn from(s: i32) -> Self {
Money { amount: s as f32 }
}
}
let money = Money::<i32>::from(100);
trait にジェネリクス
trait Deal<T> {
fn refer(val: T) -> Self;
}
impl Deal<i32> for Money<i32> {
fn refer(val: i32) -> Self {
Money { amount: val }
}
}
Into トレイト
pub trait Into<T>: Sized {
/// Converts this type into the (usually inferred) input type.
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn into(self) -> T;
}
やっと本題
into は from を実装したら、自動で実装される
let money1: Money<i32> = 10.into();
// money1 is Money { amount: 10 }
let money2: i32 = 10.into();
// money2 is 10
into は↑のような感じで使う
理解し難いポイントは戻り値を、上の例のように型で値が変わるのである
前述されているように、実態ではなく、構造体から、作ってみる
let ret1 = Into::<Money<i32>>::into(10);
// ret1 is Money { amount: 10 }
into は self を食って、T(Money<i32>)を返しているだけ
なので、
let ret2 = Into::<i32>::into(10);
// ret2 is 10
こうすると、形によって値が変わる
let ret2: i32 = Into::into(10);
// ret2 is 10
ジェネリクスで書かなくても、同じ結果である
前述したように複数の可能性があると、コンパイラは型を決められない