何とは言わない天然水飲みたさ

Rust (1.19.0) でオレオレ unsized type を定義する

結論

概要

IRI (RFC 3987, まあ URI や URL みたいなもの) で表現することになっている。 これは Unicode の文字列のサブセットであるから、 IRI を表現する型は &strString 型をベースとして値の範囲を制限した strong typedef により実装できそうである。 しかし、 str は unsized な型であるから、安直に実装しようとしてもうまくいかない。 そこで、似たような型である std::path::Path 等を参考にしつつ方法を調べた。

C/C++ で言うところの VLAIS みたいなのは未対応っぽいので、 unsized type とはいってもそういう話ではない。 (詳細は以下リンク)

strong typedef とは

このようになる。 (この例では演算子の定義をサボったが、もし整数と同様に透過的に使えるようにするなら std::ops::Add をはじめとする数々の演算子を用意することになる。めっちゃめんどい。)

より実用的でちゃんとした例としては、 ordered-float crate の NotNaN 型などがある。 これは「 NaN をとらない float (f32, f64) 型」である。 f32f64 に使える演算子はだいたいそのまま用意されており(つまり使い勝手は既存の float に劣らない)、更に追加で std::cmp::Ord 等も実装されている嬉しい型だ。

unsized な型

playground)

上のような素直な(?)実装は、当然ながらコンパイルエラーとなる。 str の値はサイズが未知であるから move できないこと、また必要なのは &self と同じ lifetime を持つ &Iri であるにも関わらず一時変数の参照を返そうとしていること、この2つがエラーの原因である。

既存の実装

pub struct Slice {
    pub inner: [u8]
}
std::sys::unix::os_str::Slice の定義
impl Slice {
    fn from_u8_slice(s: &[u8]) -> &Slice {
        unsafe { mem::transmute(s) }
    }

    pub fn from_str(s: &str) -> &Slice {
        Slice::from_u8_slice(s.as_bytes())
    }
std::sys::unix::os_str::Slice の実装
std::sys::unix::os_str::Slice

ついでに、リプで教えていただいた rocket crate の http::uncased::UncasedStr も見てみる。

#[derive(Debug)]
pub struct UncasedStr(str);
rocket::http::uncased::UncasedStr の定義
impl UncasedStr {
    /// Returns a reference to an `UncasedStr` from an `&str`.
    ///
    /// # Example
    ///
    /// ```rust
    /// use rocket::http::uncased::UncasedStr;
    ///
    /// let uncased_str = UncasedStr::new("Hello!");
    /// assert_eq!(uncased_str, "hello!");
    /// assert_eq!(uncased_str, "Hello!");
    /// assert_eq!(uncased_str, "HeLLo!");
    /// ```
    #[inline(always)]
    pub fn new(string: &str) -> &UncasedStr {
        unsafe { &*(string as *const str as *const UncasedStr) }
    }
rocket::http::uncased::UncasedStr の実装
rocket::http::uncased::UncasedStr

上に挙げた例から、本来記述すべきだった処理は、「 Iristr の値を突っ込む」ことではなく、「 Iristr を同一のものと見做し、 &str&Iri として再解釈する」ことであったことが読み取れる。

結論

playground)

あとはお好みで std::ops::Deref やら std::convert::AsRef やら諸々を実装すればおk。 まあその諸々が結構多くて手間なんだけど。

追記 (2017-08-29): 参考

くわしい。