文字列の変換自体は別記事にしました。
参考「[Rust] 文字列スライス str から文字列 String への変換とその仕組み - Qiita」
0. まとめ
- 文字列スライス
&str
- 文字列
String
let array = ["foo", "bar", "baz"]; // 配列やベクタ、スライス等
let vec: Vec<String> = array.iter().map(|&s| s.to_string()).collect(); // |s| でなく |&s| 推奨 (※理由は後述)
// or
let vec: Vec<String> = array.iter().map(|&s| String::from(s)).collect();
// or
let vec: Vec<String> = array.iter().map(|&s| s.into()).collect();
// or
let vec: Vec<String> = array.iter().map(|&s| s.to_owned()).collect();
// 確認
assert_eq!(
vec,
vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
);
let array = [10, 20, 30]; // 配列やベクタ、スライス等
let vec: Vec<String> = array.iter().map(|x| x.to_string()).collect(); // メモ: i8, u8 等では |x| でなく |&x| 推奨 (※理由は後述)
// 確認
assert_eq!(vec, vec![10.to_string(), 20.to_string(), 30.to_string()]);
1. データ変換の流れ
["foo", "bar", "baz"] : [&str; 3];
["foo", "bar", "baz"].iter() : Iter<&str>;
["foo", "bar", "baz"].iter().map(f) : Map<Iter<&str>, {{closure}}>;
["foo", "bar", "baz"].iter().map(f).collect(): Vec<String>;
[10, 20, 30] : [i32; 3];
[10, 20, 30].iter() : Iter<i32>;
[10, 20, 30].iter().map(f) : Map<Iter<i32>, {{closure}}>;
[10, 20, 30].iter().map(f).collect(): Vec<String>;
- 型強制によって、入力の型が配列等の場合もスライスのメソッドを呼べる
- スライスから
Iter
型のイテレータに変換 -
Iterator::map
メソッドでIter
型のイテレータからMap
型のイテレータに変換 -
Iterator::collect
メソッドでMap
型のイテレータからVec<String>
に変換
1.1. 型強制によるスライスへの自動型変換
配列からスライスへの Unsized 型強制や、メソッド呼び出し時の型強制等については別記事にしました。
参考「[Rust] 配列やベクタが自動でスライスに変換される仕組み - Qiita」
参考「[Rust] メソッド呼び出し時におけるメソッド探索の仕組み: 自動参照 & 自動参照外し及び Unsized 型強制 - Qiita」
let
文や関数引数等で行われる型強制とメソッド呼び出しによる型強制は仕組みが異なりますが、「Unsized 型強制」や「Deref 型強制」自体は同じで、配列やベクタ等はスライスに型強制されます。
-
[&str; N]
->[&str]
: Unsized 型強制 -
Vec<&str>
->[&str]
: Deref 型強制
※ Unsized 型強制はメソッド呼び出しではそのまま行われますが、let
文や関数引数等では参照などの「ポインタのような」型 (&[&str; N]
等) であ必要があります。
1.2. スライスからイテレータへ変換
スライスの iter
メソッドを使用して、スライス用のイテレータの実装である (Iterator
トレイトを実装する) Iter
型に変換します。
-
[&str]
->&[&str]
: メソッド呼び出し時の自動参照 -
&[&str]
->Iter<&str>
:iter
メソッドによる変換
impl<T> [T] {
// ...
pub fn iter(&self) -> Iter<'_, T> {
// ...
}
// ...
}
let array = ["foo", "bar", "baz"];
use std::slice::Iter;
let iter: Iter<&str> = array.iter();
1.3. Iter
型は Iterator
トレイトを実装
std::slice::Iter
型は core::slice::iter::iterator
マクロを用いて Iterator
トレイトを実装します。
macro_rules! iterator {
(
struct $name:ident -> $ptr:ty,
$elem:ty,
$raw_mut:tt,
{$( $mut_:tt )?},
{$($extra:tt)*}
) => {
// ...
impl<'a, T> Iterator for $name<'a, T> {
type Item = $elem;
// ...
}
// ...
}
}
iterator! {struct Iter -> *const T, &'a T, const, {/* no mut */}, {
// ...
}}
マクロの引数の値は以下のようになります。
$name = Iter
$elem = &'a T
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
// ...
}
type T = &str;
とすると、<Iter as Iterator>::Item
は &&str
型になります (本記事ではライフタイムの説明略) 。
参考「Iterator - Iter in std::slice - Rust」
1.4. map
メソッドによるイテレータから別のイテレータへ変換
map
は他のプログラミング言語でも取り入れられることが多い機能で、イテレータの各要素を別の値に変換し、同じ長さで変換された要素を持つ別のイテレータに変換します。
pub trait Iterator {
// ...
type Item;
// ...
fn map<B, F>(self, f: F) -> Map<Self, F>
where
Self: Sized,
F: FnMut(Self::Item) -> B,
{
// ...
}
// ...
}
参考「map - Iterator in std::iter - Rust」
Rust の場合は一度 Iterator
トレイトを実装する Map
型に変換し、要素を変換するクロージャをラップし、要素の値を得ようとした段階で変換が行われます (遅延評価) 。
impl<B, I: Iterator, F> Iterator for Map<I, F>
where
F: FnMut(I::Item) -> B,
{
type Item = B;
// ...
fn next(&mut self) -> Option<B> {
self.iter.next().map(&mut self.f)
}
// ...
}
※ self.iter.next().map
の map
は Option<I::Item>
型が持つメソッドで、イテレータの map
メソッドとは別物です (本記事では不明略) 。
参考「Iterator - Map in std::iter - Rust」
let array = ["foo", "bar", "baz"];
use std::slice::Iter;
let iter: Iter<&str> = array.iter();
let f = |&s: &&str| s.to_string(); // ※文字列の変換については後述
use std::iter::Map;
let map: Map<Iter<&str>, _> = iter.map(f);
ここでは文字列スライスから文字列に変換することが目的なので、関数 f
は &&str
から String
へ変換する関数です。
Map<Iter<&str>, _>
のジェネリクスは以下のように解決されます。
type I = Iter<&str>;
type I::Item = &&str;
type B = String;
type Item = String;
※関数がクロージャの場合、一意で匿名 (ユニークでアノニマス) な型になるため、型名を表現することはできません。
参考「Closure types - The Rust Reference」
1.5. イテレータは IntoIterator
トレイトをブランケット実装
IntoIterator
トレイトは「イテレータへ変換できる特性」で、イテレータ自身もブランケット実装します。
impl<I: Iterator> IntoIterator for I {
type Item = I::Item;
type IntoIter = I;
#[inline]
fn into_iter(self) -> I {
self
}
}
参考「IntoIterator - Map in std::iter - Rust」
参考「Using Trait Bounds to Conditionally Implement Methods - Traits: Defining Shared Behavior - The Rust Programming Language」(「トレイト境界を使用して、メソッド実装を条件分けする」「ブランケット実装」)
I: Iterator
(I
型は Iterator
トレイトを実装) かつ iter: I
(変数 iter
は I
型) のとき iter.into_iter()
の戻り値の型も I
型になります。
use std::slice::Iter;
let foo: Iter<i32> = [10, 20, 30].iter();
let bar: Iter<i32> = foo.into_iter();
Iterator
トレイトを実装する Map
型も IntoIterator
トレイトをブランケット実装します。
1.6. ベクタ Vec
は FromIterator
トレイトを実装
FromIterator
トレイトは「イテレータから変換できる特性」です。
IntoIterator
トレイトを実装する型、すなわち「イテレータ自身を含むイテレータに変換可能な型」から Self
型に変換する機能を持ちます。
pub trait FromIterator<A>: Sized {
// ...
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}
参考「FromIterator in std::iter - Rust」
ベクタ Vec
は FromIterator
トレイトを実装します。
impl<T> FromIterator<T> for Vec<T> {
// ...
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Vec<T> {
// ...
}
}
参考「FromIterator<T> - Vec in std::vec - Rust」
use std::slice::Iter;
let foo: Iter<i32> = [10, 20, 30].iter();
use std::iter::FromIterator;
let bar: Vec<&i32> = Vec::<&i32>::from_iter(foo);
1.7. イテレータからベクタへ変換
イテレータの collect
メソッドは、自身のイテレータを FromIterator
トレイトを実装する型へ変換します。
変換の際に FromIterator::from_iter
メソッドを呼んでいます。
pub trait Iterator {
// ...
type Item;
// ...
fn collect<B: FromIterator<Self::Item>>(self) -> B
where
Self: Sized,
{
FromIterator::from_iter(self)
}
// ...
}
参考「collect - Iterator in std::iter - Rust」
let array = ["foo", "bar", "baz"];
use std::slice::Iter;
let iter: Iter<&str> = array.iter();
let f = |&s: &&str| s.to_string(); // ※文字列の変換については後述
use std::iter::Map;
let map: Map<Iter<&str>, _> = iter.map(f);
let vec: Vec<String> = map.collect();
ここで、map.collect()
のジェネリクスは以下のように解決されます。
type Item = String;
type B = Vec<String>;
メソッド呼び出しを繋げると、以下のようになります。
let array = ["foo", "bar", "baz"];
// ※ map メソッドに渡すクロージャの中身については後述
let vec: Vec<String> = array.iter().map(|&s: &&str| s.to_string()).collect();
2. 文字列 String
に変換する方法
2.1. 文字列スライス str
から文字列 String
の変換
Rust における文字列スライス str
から文字列 String
の変換は主に以下の 4 つです。
(※他にも、大文字や小文字に変換するメソッド等もあります。)
s: &str
のとき:
s.to_string()
String::from(s)
s.into()
s.to_owned()
詳細は別記事にしました。
参考「[Rust] 文字列スライス str から文字列 String への変換とその仕組み - Qiita」
2.2. 文字列以外から文字列 String
の変換
フォーマットを指定せずに単に文字列に変換したい場合には to_string
メソッドを使用します。
2.3. to_string
メソッドの注意
to_string
メソッドでは自動参照外しされません。
参考「[Rust] to_string メソッド等は呼び出し時に自動参照外しされない - Qiita」
これにより、&str
と &&str
で呼ばれる to_string
が異なる (文字列に変換する具体的な処理が異なる) 等が起きます (バージョン 1.55.0 現在) 。
参考「[Rust] &str と &&str で呼ばれる to_string メソッドが異なる - Qiita」
str
以外でも同様の現象が起きることがあります。
-
char
,str
,String
Cow<str>
-
i8
,u8
参考「Implementors - ToString in std::string - Rust」(ToString
トレイトを独自に実装する型等)
処理速度を気にするのであれば、明示的に参照外しをするか、型を修飾してメソッドを呼ぶ方が良いです。
3. 文字列 String
に変換する関数またはクロージャを定義
Iter
ではイテレータの要素の型に参照が付きます。
例:
type <Iter<&str> as Iterator>::Item = &&str;
type <Iter<i32> as Iterator>::Item = &i32;
3.1. to_string
メソッドを使用する場合
to_string
メソッドは明示的な参照外しをしなくてもコンパイルエラーになりませんが、前述の通り、型が独自に ToString
トレイトを実装している場合を考えて明示的に参照外しするか型を修飾します。
関数やメソッドの引数において「パターン」を用いて参照外しすることができます。
参考「[Rust] 「パターン」を用いた非構造化変数束縛 - Qiita」
// &str の場合
let f = |&s: &&str| s.to_string(); // 参照パターンによる参照外し
// or
let f = |s: &&str| (*s).to_string(); // 参照外し演算子 * による参照外し
// or
let f = |s: &&str| str::to_string(s); // 型を修飾して自動参照外し
// or
let f = |s: &&str| <str as ToString>::to_string(s); // 完全修飾構文で自動参照外し
// etc.
// i32 の場合
let f = |x: &i32| x.to_string();
※ Iterator::map
メソッドの引数に直接クロージャを書く場合は、クロージャの引数の型を省略できます。
3.2. String::from
メソッドや <&str>::into
メソッドを使用する場合
from
と into
は対応する機能です。
どちらもジェネリック型を使用していて、その型を解決できる必要があります。
&&str
から変換しようとするとジェネリクスを解決できず、コンパイルエラーになります。
明示的に参照外しするか型を修飾して対応します。
let f = |&s: &&str| String::from(s); // 参照パターンによる参照外し
// or
let f = |s: &&str| String::from(*s); // 参照外し演算子 * による参照外し
// or
let f = |s: &&str| -> String { From::<&str>::from(s) }; // 型を修飾して自動参照外し
// or
let f = |s: &&str| <String as From<&str>>::from(s); // 完全修飾構文で自動参照外し
// etc.
let f = |&s: &&str| -> String { s.into() }; // 参照パターンによる参照外し
// or
let f = |s: &&str| -> String { (*s).into() }; // 参照外し演算子 * による参照外し
// or
let f = |s: &&str| -> String { <&str>::into(s) }; // 型を修飾して自動参照外し
// or
let f = |s: &&str| -> String { <&str as Into<_>>::into(s) }; // 型を修飾して自動参照外し
// or
let f = |s: &&str| <&str as Into<String>>::into(s); // 完全修飾構文で自動参照外し
// etc.
※ Iterator::map
メソッドの引数に直接クロージャを書く場合は、クロージャの引数の型や戻り値の型を省略できます。
3.3. str::to_owned
メソッドを使用する場合
ToOwned
トレイトは参照に対して再帰的にブランケット実装され、実装している型によって戻り値の型が異なるため、明示的に参照を外すか型を修飾しないとコンパイルエラーになります。
※ ToOwned::to_owned
メソッドの戻り値の型は Self
でなく Self::Owned
のため、戻り値の型から self
の型を決定できません。
参考「[Rust] to_string メソッド等は呼び出し時に自動参照外しされない - Qiita」
let f = |&s: &&str| s.to_owned(); // 参照パターンによる参照外し
// or
let f = |s: &&str| (*s).to_owned(); // 参照外し演算子 * による参照外し
// or
let f = |s: &&str| str::to_owned(s); // 型を修飾して自動参照外し
// or
let f = |s: &&str| <str as ToOwned>::to_owned(s); // 完全修飾構文で自動参照外し
// etc.
※ Iterator::map
メソッドの引数に直接クロージャを書く場合は、クロージャの引数の型を省略できます。