Deref
, DerefMut
Deref
と DerefMut
はある型を透過的に別の型への参照として振る舞わせるもので、非常に便利である一方、 strong typedef の「内部的には同じ型を別個のものとして区別させる」という目的と反するものでもある。
標準ライブラリのドキュメントでは、これらのトレイトはスマートポインタのみに実装すべきであると強調されている。
Implementing
Deref
for smart pointers makes accessing the data behind them convenient, which is why they implementDeref
. On the other hand, the rules regardingDeref
andDerefMut
were designed specifically to accommodate smart pointers. Because of this,Deref
should only be implemented for smart pointers to avoid confusion.——Rust 1.48.0 の
std::ops::Deref
のドキュメント。 強調は原文ママ
たとえば str
は内部的には [u8]
であるが、 Deref<Target=[u8]> for str
は実装されていない。
このような「透過的に同一視されてほしいわけではない型」は AsRef<[u8]>
だったり .as_bytes()
で明示的な変換を行うべしということである。
一方で、 &String
が透過的に &str
として扱えてほしいとか Vec<u8>
を透過的に &[u8]
として扱いたいとかは全くもって正当な要求である。
実際、標準ライブラリでも Deref<Target=str> for String
とか Deref<Target=[u8]> for Vec<u8>
などの実装がされている。
よって、所有権付きの独自型から所有権なしのスライス型への Deref
による変換は一般的に実装すべきである。
#![allow(unused)] fn main() { // 独自スライス型への暗黙の変換を許容する。 #[repr(transparent)] pub struct MyStr(str); #[derive(Clone)] pub struct MyString(String); impl MyString { fn as_my_str(&self) -> &MyStr { unimplemented!() } fn as_my_str_mut(&mut self) -> &mut MyStr { unimplemented!() } } impl std::ops::Deref for MyString { type Target = MyStr; #[inline] fn deref(&self) -> &Self::Target { self.as_my_str() } } impl std::ops::DerefMut for MyString { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.as_my_str_mut() } } }
追加の制約の有無に関係なく同じような実装になるため、 AsciiString
と AsciiByteBuf
への実装例は省略する。
一応例としてコードは載せるが、特に所有権のないスライス型に本当に Deref
を実装すべきかは熟慮すべきである。
困ったら実装せずにおくのがよい。 後から std
や core
のトレイトを実装しても breaking change にはならないはずである。
#![allow(unused)] fn main() { // 独自スライス型への Deref と DerefMut の実装。 #[repr(transparent)] pub struct MyStr(str); // Do you really want this? impl std::ops::Deref for MyStr { type Target = str; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } // Do you really REALLY want this? impl std::ops::DerefMut for MyStr { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } }
再三繰り返すが、追加の制約がある型の場合、くれぐれも内側の型 (MyStr
の例であれば str
) への mutable 参照を safe に露出させてはいけない。
たとえば AsciiStr
から DerefMut
経由で &mut str
を露出させるのは厳禁である。
例のごとく、 AsciiStr
と AsciiBytes
への実装例は省略する。