現時点での知見としては、
* トレイト実装の自動化は、 #[derive(..)] ではなく impl<..> Trait<..> for Ty<..> {} のような実際の文法に近い形で指定しないとかなりつらい
* underlying slice type と conversion source は完全に分離しないとつらい
などがある #らりおメモ
underlying と source の分離というのは、たとえば struct DatetimeStr(str); と struct DatetimeStr([u8]); のどちらも可能だし状況次第でどちらにも優位性があるのでどうするか、というのが underlying type の選択。
DatetimeStr::try_from(&str) と DatetimeStr::try_from(&[u8]) の両方が可能だけどどう実装しようか、というのが conversion source の問題。
impl TryFrom<&[u8]> for DatetimeStr {
type Error = ..;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
core::str::from_utf8(bytes)
.map_err(Self::Error::from)
.and_then(Self::try_from)
}
}
みたいにして conversion source をまとめることもできるんだが、これだと parsing で重複したコストがかかることになって非効率な場合があるので、実はよろしくない
たとえば「バイト列に対して実装されたパーサで validation を行っているが文字列の内部では str を持ちたい」みたいな場合に str としての validation は完全に無駄になる
で、そもそもそのような場合だと &[u8] に対して validation したことをもって &str としても valid であるという知識をユーザが暗黙に与えることになるので、たとえばパーサのバグどころかマクロの使い方のミス程度のことでたやすく UB になりかねない。仕方ないといえば仕方ないかもしれないが、さすがにそれはライブラリとして微妙すぎる
自動実装者が持つべき知識をちゃんと表現できないと永遠につらいままだな、これ
尤もマクロの使い方の話をするなら、そもそも #[repr(transparent)] を型定義で忘れていても derive macros でない限り型定義のトークン列を得られないのでどうしようもないなどの問題もあり、一番最初の段階から既にどうしようもなさがあるが
だんだん思い出してきた、 owned types と borrowed slice types の相互作用とか実装の移譲も考えないといけないのだった……
あと Partial{Eq,Ord} の移譲も……