はじめに
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関数で可変参照を取得して文字列を追加したタイミングで、文字列リテラルがヒープ領域にコピーされその領域に書き込みを行っています。
このように必要な時にデータのコピーが行われるので、あらかじめヒープ領域を確保しておくといったことが不要になります。
参考文献