hiro99ma blog

rust: 別の型に変換するルールを分かりたい

Rust は型の不一致判定が厳しいので(C言語の人目線)、何かと型を変換する作業が発生する。
&strString にするのは .to_string() を使うが、&String&str としても使えるのは deref があるからだ。
そうかと思えば .as_str() みたいに “as” だったり、 ::from() のようなものもある。
::new() も引数があったりなかったりするが、引数があるなら ::from() でもいいんじゃないのかなどと考えたりする。
それ以外にも、文字列から別の型を作り出す場合に "文字列".ほにゃほにゃ() という &strString にはないメソッドを呼び出していると思われる箇所を見たこともある。

こういった変換についてのあれこれを把握しておかないと、今のところいちいち AI などに訊いていて効率が悪いのだ。

なんとなく

.as_~().to_~() はなんとなくわかる気がする。
“as” はキャストっぽい感じで「~とみなす」ということだろう。使っても負荷がほとんど無いのだと思う。
“to” は「(今のから)~にする」といった感じだろう。as じゃないなら何かデータの移し替えなどが発生すると思っている。
.to_~() が今のデータを別のデータに変換するなら、~::from() は別のデータから今のデータ(Self)に変換する方向だろう。
名前についてのガイドラインがあった。

image

String の Example を見ると .to_string() もあれば String::from() もある。
.into() というのもあるのか。。。
まあちょっとそっちは置いておくとして、from()String の関数ではなく FromトレイトFrom<&str> らしい。
そして .to_string()ToStringトレイト なのかな?

よく考えると "abc".to_string()&str 側に実装があるはずだ。
自作のクレート UmaUma を作っても "abc".to_umauma()&str に作ってもらうのは無理だろう。
UmaUma::from("abc") であれば自分でも作ることはできそうだ。
たぶん "abc".to_umauma() も作ることができるのではないかと思う。どうだろう。 .parse::<UmaUma>() はできそうな感じがする。

as

.as_~() ではなく単なる as

主にプリミティブ型のキャストや use の別名のために使われる。

From と Into

FromInto もトレイトだそうだ。

From

std::convert::From が本名?のようで、中身はこんな感じ。

pub trait From<T>: Sized {
    /// Converts to this type from the input type.
    #[rustc_diagnostic_item = "from_fn"]
    #[must_use]
    #[stable(feature = "rust1", since = "1.0.0")]
    fn from(value: T) -> Self;
}

よく使いそうな String::from() はこうなっていた。

impl From<&str> for String {
    /// Converts a `&str` into a [`String`].
    ///
    /// The result is allocated on the heap.
    #[inline]
    fn from(s: &str) -> String {
        s.to_owned()
    }
}

.to_owned() は別のトレイト ToOwned で、実装はこう。

impl ToOwned for str {
    type Owned = String;

    #[inline]
    fn to_owned(&self) -> String {
        unsafe { String::from_utf8_unchecked(self.as_bytes().to_owned()) }
    }

    #[inline]
    fn clone_into(&self, target: &mut String) {
        target.clear();
        target.push_str(self);
    }
}

まだ .to_owned() がある。。。

impl<T: Clone> ToOwned for [T] {
    type Owned = Vec<T>;

    fn to_owned(&self) -> Vec<T> {
        self.to_vec()
    }

    ......
}

.to_vec()

    pub fn to_vec(&self) -> Vec<T>
    where
        T: Clone,
    {
        self.to_vec_in(Global)
    }

この次の .to_vec_in() が大きい。 大きいというか、中に trait が 1つと impl が 2つもある。
StringVec<u8> をメンバーに持つだけなので最悪でもデータをコピーするだけだと思っていたのにこんなに大変なんだ。

おまけ : to_string()

では .to_string() もこのくらい大変なのだろうか?

こちらは Display トレイトだ。
vscode でジャンプしたのだが、ToString トレイトじゃなかったのか?

impl Display for str {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        f.pad(self)
    }
}

ホバーさせると ToString for T だな。トレイト境界の方のコードに飛んだのだろうか。

image

.pad() も 40行くらいあるそこそこ大きめの関数だ。 手軽に使っていたけど、どうなんだろうか。
ChatGPT氏 からすると、inline なっていたりして実質的に重たくはないらしい。
ただよくだまされるから、信用しづらいなあ。

(更新中。。。)

writer: hiro99ma
tags: Rust言語

 
About me
About me
comment
Comment Form
🏠
Top page
GitHub
GitHub
Twitter
X/Twitter
My page
Homepage