はじめに
こんにちは。細々とプログラミングをしているsotanengelです。
この記事は以下の記事の連載です。
他の連載記事 (詳細)
また本記事はEffective Rust(David Drysdale (著), 中田 秀基 (翻訳))を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください!
今日の内容
概要
Rustには以下の3つの型変換があります。
これらの変換のうち、ユーザーが定義した型に対する変換処理を実装します。
- 手動
- ユーザーが実装する
From
やInto
のこと
- ユーザーが実装する
- 半自動
-
as
による明示的なキャスト
-
- 自動
- 自動型変換
自作の型にFrom
トレイトを実装する
問(リンク)
RustではFrom
トレイトを実装することでInto
も自動的に実装されます。
MyStruct
にFrom
トレイトを実装しましょう。
コード (詳細)
#[derive(Debug)]
#[warn(dead_code)]
struct MyStruct {
value: String,
}
// TODO: MyStructにFrom<String>トレイトを実装して、main内部でintoが使えるようにしてください。
fn main() {
let s = String::from("Hello");
let my_struct: MyStruct = s.into();
println!("{:?}", my_struct);
}
解答(リンク)
コード参照。
コード (詳細)
#[derive(Debug)]
#[allow(dead_code)]
struct MyStruct {
value: String,
}
// Fromトレイトを実装することでIntoが自動実行されます。
impl From<String> for MyStruct {
fn from(s: String) -> Self {
MyStruct { value: s }
}
}
fn main() {
let s = String::from("Hello");
let my_struct: MyStruct = s.into();
println!("{:?}", my_struct);
}
トレイト制約にはInto
を使おう
問(リンク)
print_length
内部で引数をInto
で変更しましょう。
コード (詳細)
fn print_length<T: Into<String>>(value: T) {
// TODO: intoを使って、valueを変換した値を変数sに格納してください。
println!("Length: {}", s.len());
}
fn main() {
let s = "Hello".to_string();
let str_slice = "Goodbye";
print_length(s);
print_length(str_slice);
}
解答(リンク)
Into
を使うことで以下の両方に対応することができます。
-
From
トレイトを実装しているもの -
Into
トレイトを実装しているもの
コード (詳細)
fn print_length<T: Into<String>>(value: T) {
let s: String = value.into(); // トレイト制約を実行する際にはIntoを使う。
println!("Length: {}", s.len());
}
fn main() {
let s = "Hello".to_string();
let str_slice = "Goodbye";
print_length(s);
print_length(str_slice);
}
反射実装をする
問(リンク)
反射実装を使ってジェネリック関数での変換を省略しましょう。
コード (詳細)
// ドメイン名を表す構造体
#[derive(Debug)]
pub struct DomainName(pub String);
// StringからDomainNameへの変換
impl From<String> for DomainName {
fn from(value: String) -> Self {
Self(value)
}
}
// &str からも直接変換できるようにする
impl From<&str> for DomainName {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
// ドメインが example.com のサブドメインかを判定する関数
// TODO: 反射実装を使って、この関数にトレイト制約をInto<DomainName>に課すことで
// 「String」「&str」「DomainName」を引数に入れたとしても対応できるようにしてください。
pub fn is_example_subdomain<T>(domain: T) -> bool {
let domain = domain.into();
domain.0.ends_with(".example.com")
}
fn main() {
let domain1 = DomainName("sub.example.com".to_string());
let domain2 = "other.com".to_string();
let domain3 = "api.example.com";
// DomainName構造体を直接渡す
println!("sub.example.com? {}", is_example_subdomain(domain1));
// Stringを渡す (Intoで自動変換)
println!("other.com? {}", is_example_subdomain(domain2));
// &strを渡す (Intoで自動変換)
println!("api.example.com? {}", is_example_subdomain(domain3));
}
解答(リンク)
コード参照。
コード (詳細)
// ドメイン名を表す構造体
#[derive(Debug)]
pub struct DomainName(pub String);
// StringからDomainNameへの変換
impl From<String> for DomainName {
fn from(value: String) -> Self {
Self(value)
}
}
// &str からも直接変換できるようにする
impl From<&str> for DomainName {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
// ドメインが example.com のサブドメインかを判定する関数
pub fn is_example_subdomain<T>(domain: T) -> bool
where
// ジェネリック T を受け取り、T が Into<DomainName> トレイトを実装していれば受け入れます。
// つまりString や &str など DomainName に変換可能な型を引数に渡せます。
// またRustは自身への変換 (Into<DomainName>) は自動で実装されています。
T: Into<DomainName>,
{
let domain = domain.into();
domain.0.ends_with(".example.com")
}
fn main() {
let domain1 = DomainName("sub.example.com".to_string());
let domain2 = "other.com".to_string();
let domain3 = "api.example.com";
// DomainName構造体を直接渡す
println!("sub.example.com? {}", is_example_subdomain(domain1));
// Stringを渡す (Intoで自動変換)
println!("other.com? {}", is_example_subdomain(domain2));
// &strを渡す (Intoで自動変換)
println!("api.example.com? {}", is_example_subdomain(domain3));
}
さいごに
もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。