Borrow, BorrowMut, ToOwned

これらはスライス型を独自に定義するうえで特に使い勝手に影響するトレイトである。 具体的には、 .to_owned() が使えるようになる1のと、 std::borrow::Cow が使えるようになる。


#![allow(unused)]
fn main() {
//  所有権なしのスライス型と所有権付きの型を相互に紐付ける。

#[repr(transparent)]
pub struct MyStr(str);
impl MyStr {
    fn as_str(&self) -> &str { unimplemented!() }
}

pub struct MyString(String);
impl MyString {
    fn as_my_str(&self) -> &MyStr { unimplemented!() }
    fn as_my_str_mut(&mut self) -> &mut MyStr { unimplemented!() }
    fn new(s: String) -> Self { unimplemented!() }
}

impl core::borrow::Borrow<MyStr> for MyString {
    #[inline]
    fn borrow(&self) -> &MyStr {
        self.as_my_str()
    }
}

impl core::borrow::BorrowMut<MyStr> for MyString {
    #[inline]
    fn borrow_mut(&mut self) -> &mut MyStr {
        self.as_my_str_mut()
    }
}

impl std::borrow::ToOwned for MyStr {
    type Owned = MyString;

    fn to_owned(&self) -> Self::Owned {
        MyString::new(self.as_str().to_owned())
    }
}
}

Borrow<T>T 型の参照を取り出すためのトレイトである。 BorrowMut<T> は mutable 参照を取り出す版。

ToOwned は、参照型から所有権付きの型 (と値) を得るためのトレイトである。 Clone トレイトが実装されてさえいれば、 &T の所有権付きの型 T の値を .clone() によって得られる。 しかし [T]strMyStr などのスライス型は DST であり Clone トレイトを実装できず、参照を外した型の値をそのまま保持できないから、代わりに何かしらのバッファ的な型が必要になる。 これは [T] の場合は Vec<T> であり、 str の場合は String であり、同様に MyStr に対しては MyString を用意してやろうということである。

同じようなコードになるが、一応 unsafe を使うことになるので AsciiStr のコード例も確認しておこう。


#![allow(unused)]
fn main() {
// ほとんど同じだが unsafe を使っている部分がある。

#[repr(transparent)]
pub struct AsciiStr(str);
impl AsciiStr {
    fn as_str(&self) -> &str { unimplemented!() }
}

pub struct AsciiString(String);
impl AsciiString {
    fn as_ascii_str(&self) -> &AsciiStr { unimplemented!() }
    fn as_ascii_str_mut(&mut self) -> &mut AsciiStr { unimplemented!() }
    unsafe fn new_unchecked(s: String) -> Self { unimplemented!() }
}

impl core::borrow::Borrow<AsciiStr> for AsciiString {
    #[inline]
    fn borrow(&self) -> &AsciiStr {
        self.as_ascii_str()
    }
}

impl core::borrow::BorrowMut<AsciiStr> for AsciiString {
    #[inline]
    fn borrow_mut(&mut self) -> &mut AsciiStr {
        self.as_ascii_str_mut()
    }
}

impl std::borrow::ToOwned for AsciiStr {
    type Owned = AsciiString;

    fn to_owned(&self) -> Self::Owned {
        let s = self.as_str();
        unsafe {
            // SAFETY: Valid `AsciiStr` string is also valid as `AsciiString`.
            AsciiString::new_unchecked(s.to_owned())
        }
    }
}
}

&AsciiStr から直接 AsciiString を作れないため &strString を経由する。 これ自体は MyStr の例と同じだが、今回の例では String から AsciiString を作るのが unsafe な操作である。 不変条件を満たしていること自体は明らかなので、コメントで明示したうえで素直に書けばよい。


1

実は "hello".to_owned() などとした場合、 <str as ToOwned>::to_owned("hello") が呼ばれている。 存在感の薄いトレイトだが、 ToOwned は prelude に入っているのである。