はじめに
Rustでは文字列リテラル(&str
)と文字列(String
)は別の型です。
引数の型が&str
の場合、String
は&
を付けて参照を渡す必要があります。
fn print(s: &str) {
println!("{}", s);
}
fn main() {
let s1 = "Hello &str!!";
let s2 = "Hello String!!".to_string();
print(s1);
print(&s2);
}
引数の型がString
の場合、&str
はto_string
でString
に変換する必要があります。
fn print(s: String) {
println!("{}", s);
}
fn main() {
let s1 = "Hello &str!!";
let s2 = "Hello String!!".to_string();
print(s1.to_string());
print(s2);
}
この両方を渡す方法は色々ありますが、今回はCow
を使用した方法を紹介します。
結論
Into<Cow<'a, str>>
というトレイト境界で両方とも渡せるようになります。
use std::borrow::Cow;
fn print<'a, T: Into<Cow<'a, str>>>(s: T) {
println!("{}", s.into());
}
fn main() {
let s1 = "Hello &str!!";
let s2 = "Hello String!!".to_string();
print(s1);
print(s2);
}
Cowって何?🐄
Cow
とはClone on writeの略で「参照の保持」と「値の所有」のどちらも可能な列挙体です。
以下の様に定義されています。
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
Cow::Borrowed
には文字列リテラルを、Cow::Owned
には文字列を渡します。
let c1: Cow<'_, str> = Cow::Borrowed("Literal");
let c2: Cow<'_, str> = Cow::Owned("String".to_string());
Cow::from
メソッドから生成もできます。
let c1: Cow<'_, str> = Cow::from("Literal"); // Cow::Borrowed
let c2: Cow<'_, str> = Cow::from("String".to_string()); // Cow::Owned
Cow
を使うことで、型を抽象化して扱うことが可能です。
遅延クローン
Cow
には遅延クローンという便利な機能があります。
参照に対して書き込みをしたタイミングで、データのコピーが行われる機能です。
use std::borrow::Cow;
fn print_cow<'a>(c: &Cow<'a, str>) {
match c {
Cow::Borrowed(s) => {
println!("Cow::Borrowed {s}");
}
Cow::Owned(s) => {
println!("Cow::Owned {s}");
}
}
}
fn main() {
let s = "Hello, World!!";
let mut cow = Cow::Borrowed(s);
print_cow(&cow);
cow.to_mut().push_str("Push String"); // ここで文字列リテラルがヒープ領域にコピーされる
print_cow(&cow);
}
上記のコードの実行結果は以下の様になります。
Cow::Borrowed Hello, World!!
Cow::Owned Hello, World!!Push String
文字列リテラルの参照を保持していた変数cow
ですが、文字列の追加後は値の所有に変わっています。
to_mut
関数で可変参照を取得して文字列を追加したタイミングで、文字列リテラルがヒープ領域にコピーされその領域に書き込みを行っています。
このように必要な時にデータのコピーが行われるので、あらかじめヒープ領域を確保しておくといったことが不要になります。
参考文献