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へ型変換
前回の投稿で
- 参照だけでいい場合は、&strを使う
- 文字列の中身を書き換えたい場合は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_string
は iter
の外でto_stringしてしまうと string_to_string
と同じことになってしまうので、 iter
内で to_string
してみました。
当然、これが一番遅いですね。
ここにもかいてあるけど
しかしながら、パフォーマンス上ではスライスの作成はコピーが発生しませんが、Stringの作成はスライスの内容を一旦ベクタに変換する必要があります
to_string
は slice -> Vector なので、メモリ再割当てが発生しているということですかね。
まとめ
- 呼び出し元の変数の型がStringの場合は、&strでも&Stringでも、さほど変わらないので、受け取る側で使いやすい方で良い
- 呼び出し元の変数の型が&strの場合は、&Stringで受け取ろうとすると若干不利になる
- つまり、受け取る側で&strで良いのであれば (Stringでないと困る理由がなければ) &strにしておいた方が効率的
ということだろうか。
結局前回の
参照だけでいい場合は、&strを使う
文字列の中身を書き換えたい場合はStringを使うとよい
と同じことですね。
(というのを再認識した)
(文字列の中身を書き換えたい場合
以外にもStringにしかないメソッドを使いたいとかもあるかも)