LoginSignup
17
9

More than 5 years have passed since last update.

[Rust] 引数で文字列の参照を受取る場合に&strと&Stringどちらが良いのか

Last updated at Posted at 2018-03-25

前回 の投稿でコメント頂いた所で、新たな疑問が浮かんだ。

fn puts(s: &str) {
    println!("puts:       {:p}", s);
}

let a = "hoge".to_string();
println!("&String:    {:p}", &a);
println!("UTF-8 addr: {:p}", a.as_ptr()); // StringのUTF-8バイト列へのポインタ
println!("&str:       {:p}", a.as_str()); // Stringから&strを得る
puts(&a);                                 // &Stringから&strへ型強制
println!("&str:       {:p}", &a as &str); // &Stringから&strへ型変換

前回の投稿で

  1. 参照だけでいい場合は、&strを使う
  2. 文字列の中身を書き換えたい場合はStringを使うとよい

という理解に至ったけど、
じゃあ上記 1 の場合 に &str&String で受け取るのは、何が違うのか。

検証環境

OS: macOS High Sierra 10.13.3
Rust: rustc 1.26.0-nightly (c08480fce 2018-03-23)

引数で&strと&Stringを受け取った時のポインタを見てみる

fn puts_str(s: &str) {
    println!("{:p}", s);
}

fn puts_string(s: &String) {
    println!("{:p}", s.as_ptr());
}

let a = "hoge".to_string();
puts_str(&a);
puts_string(&a);

※前回コメント頂いたコードで、as_ptr で見ればいい事を知った!

0x100a17018
0x100a17018

同じですね。

&strで受け取る場合と&Stringで受け取る場合でベンチマーク取ってみる

呼び出し元の変数の型がStringの場合

extern crate test;

fn print_str(s: &str) {
    println!("{:p}", s);
}

fn print_string(s: &String) {
    println!("{:p}", s);
}

#[cfg(test)]
mod tests {
    use super::*;
    use self::test::Bencher;

    #[bench]
    fn bench_string_to_str(b: &mut Bencher) {
        let s = "hoge".to_string();
        b.iter(|| print_str(&s) );
    }

    #[bench]
    fn bench_string_to_string(b: &mut Bencher) {
        let s = "hoge".to_string();
        b.iter(|| print_string(&s) );
    }
}

何度か実行してみた

cargo bench
running 2 tests
test str_vs_string::tests::bench_string_to_str    ... bench:         176 ns/iter (+/- 42)
test str_vs_string::tests::bench_string_to_string ... bench:         176 ns/iter (+/- 37)
running 2 tests
test str_vs_string::tests::bench_string_to_str    ... bench:         174 ns/iter (+/- 65)
test str_vs_string::tests::bench_string_to_string ... bench:         184 ns/iter (+/- 44)
running 2 tests
test str_vs_string::tests::bench_string_to_str    ... bench:         183 ns/iter (+/- 24)
test str_vs_string::tests::bench_string_to_string ... bench:         184 ns/iter (+/- 43)

型強制にはいくつかの規則があるのですが、その中の Deref による型強制 が働いて、&String から &str へ暗黙的に変換されます。

ということだったので、型変換がある分、&strの方が遅いかと思ったけど、
これだけだと、ほぼ変わらなかった。(といっても過言ではない気がする)

呼び出し元の変数の型が&strの場合

extern crate test;

fn print_str(s: &str) {
    println!("{:p}", s);
}

fn print_string(s: &String) {
    println!("{:p}", s);
}

#[cfg(test)]
mod tests {
    use super::*;
    use self::test::Bencher;

    #[bench]
    fn bench_string_to_str(b: &mut Bencher) {
        let s = "hoge".to_string();
        b.iter(|| print_str(&s) );
    }

    #[bench]
    fn bench_string_to_string(b: &mut Bencher) {
        let s = "hoge".to_string();
        b.iter(|| print_string(&s) );
    }

    // 追加
    #[bench]
    fn bench_str_to_str(b: &mut Bencher) {
        let s = "hoge";
        b.iter(|| print_str(&s) );
    }
    // 追加
    #[bench]
    fn bench_str_to_string(b: &mut Bencher) {
        let s = "hoge";
        b.iter(|| print_string(&s.to_string()) );
    }
}
running 4 tests
test str_vs_string::tests::bench_str_to_str       ... bench:         174 ns/iter (+/- 28)
test str_vs_string::tests::bench_str_to_string    ... bench:         217 ns/iter (+/- 49)
test str_vs_string::tests::bench_string_to_str    ... bench:         176 ns/iter (+/- 10)
test str_vs_string::tests::bench_string_to_string ... bench:         177 ns/iter (+/- 29)
running 4 tests
test str_vs_string::tests::bench_str_to_str       ... bench:         176 ns/iter (+/- 41)
test str_vs_string::tests::bench_str_to_string    ... bench:         227 ns/iter (+/- 60)
test str_vs_string::tests::bench_string_to_str    ... bench:         175 ns/iter (+/- 55)
test str_vs_string::tests::bench_string_to_string ... bench:         186 ns/iter (+/- 50)

running 4 tests
test str_vs_string::tests::bench_str_to_str       ... bench:         175 ns/iter (+/- 42)
test str_vs_string::tests::bench_str_to_string    ... bench:         226 ns/iter (+/- 142)
test str_vs_string::tests::bench_string_to_str    ... bench:         185 ns/iter (+/- 53)
test str_vs_string::tests::bench_string_to_string ... bench:         204 ns/iter (+/- 60)

str_to_stringiter の外でto_stringしてしまうと string_to_string と同じことになってしまうので、 iter 内で to_string してみました。
当然、これが一番遅いですね。

ここにもかいてあるけど

しかしながら、パフォーマンス上ではスライスの作成はコピーが発生しませんが、Stringの作成はスライスの内容を一旦ベクタに変換する必要があります

to_string は slice -> Vector なので、メモリ再割当てが発生しているということですかね。

まとめ

  • 呼び出し元の変数の型がStringの場合は、&strでも&Stringでも、さほど変わらないので、受け取る側で使いやすい方で良い
  • 呼び出し元の変数の型が&strの場合は、&Stringで受け取ろうとすると若干不利になる
  • つまり、受け取る側で&strで良いのであれば (Stringでないと困る理由がなければ) &strにしておいた方が効率的

ということだろうか。

結局前回の

参照だけでいい場合は、&strを使う
文字列の中身を書き換えたい場合はStringを使うとよい

と同じことですね。
(というのを再認識した)
(文字列の中身を書き換えたい場合 以外にもStringにしかないメソッドを使いたいとかもあるかも)

17
9
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9