2017年7月5日水曜日

The Rust Programming Language 2nd 11日目

![](https://doc.rust-lang.org/book/second-edition/]
Apache License Version 2.0

8. Common Collections

Strings

What is String?

Rustが実装している文字列型はstrのみで,ふつう&strで利用される.StringはRustの標準ライブラリに実装されていて,ともにUTF-8でエンコードされている.

Creating a New String

Vecと同じく,newによって新しいStringを定義できる.
let s = String::new();
sという空の文字列を新たに宣言する.また,変数に入れたい文字列が決まっているときは,

let data = "initial contents";

let s = data.to_string();
// これらは結局同じこと
let s = "initial contents".to_string();

このように,to_string methodによって,String型の変数を作れる.to_stringDisplayを実装している型なら持っている.また,String::fromで文字列リテラルを受け取るとStringにできることはすでに見た.

let s = String::from("initial contents");

Updating a String

Vecと同じくStringは伸び縮みできるし,その中身自体を変更することもできる.特に,+演算子によってString同士をつなげることができる.

Appending to a String with Push

Stringpush_str methodによって末尾にさらに文字列を追加できる.

fn main() {
  let mut s = String::from("foo");
  s.push_str("bar");

  println!("s is {}", s);
}

shell

s is foobar

さらに,string literalはStringのreferenceだから,(Chap. 4)

fn main() {
  let mut s1 = String::from("foo");
  let s2 = String::from("bar");
  s1.push_str(&s2);                   // referenceでないとエラー

  println!("s1 is {}", s1);
}

としても同じことである.ただし,push_strはreferenceのみを引数に取る.
また,pushは引数に1文字だけをとる.

fn main() {
  let mut s1 = String::from("lo");
  s1.push('l');                       // 'lo'などとするとエラー

  println!("s is {}", s1);
}

Concatenation with the + Operator or the format! Macro

2つのStringを結合させたいことがある.前節とは別の方法に,+によるconcatenationがある.

let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1はmoveしてこの時点で消滅する.s2は必ずreference

このようにしてs3の中身はHello, World!となる.+演算子は内部的には

fn add(self, s: &str) -> String{ // 本当はもっと高度なことをしているらしい(Chap. 10)

のような,Stringadd methodを使っているので,ownershipのmoveによってs1は消えてしまう.ところで,&s2の型は&Stringで,&strではない.addの引数として&Stringを与えると内部でderef coercionという機能が働いて&strに変換(原文coerce)する.(&s2 => &s2[..] にする.strとはStringのsliceであった.)
また,addは引数のselfのownershipをとっていて,s1は消える.
2つ以上のStringを結合したいときは,let s = s1 + "-" + &s2 + "-" + &s3;などとする.またより読みやすい記法としてlet s = format!("{}-{}-{}", s1, s2, s3);としても同じことである.

Indexing into Strings

rustはStringのindexを指定して文字を取り出す機能を実装していない.

Internal Representation

StringVec<u8>のラッパである.つまり,UTF-8のコードの数値をVectorに保持し,それを文字列として扱うための糖衣構文である.

let len = String::from("Hola").len();

とすると,lenは4である.これはUTF-8でラテン文字は1Byteで表現されるためで,例えば

let len = String::from("Здравствуйте").len();

とすると,キリル文字は2Bytesで表現されるのでlen24となる.ここで

let hello = "Здравствуйте";
let answer = &hello[0];       // 実際はコンパイルできない

とすると,ЗのUTF-8表現は208なので,answer208となって,これは大方のプログラマの予期しない結果と思わるので,Rustはこうした行為ができないようにしている.

Bytes and Scalar Values and Grapheme Clusters! Oh my!

Grapheme Clustersとは,我々が単にletters(文字列?)と呼ぶものに最も近いRust内の表現である.
“नमस्ते”というヒンズー語の文字列は,Rust内部では究極的に
“`rust
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]

という```Vec<u8>```型である.
これは18bytesあって,ヒンズー語の文字としてそれぞれを分けてみると,
```rust
['न', 'म', 'स', '्', 'त', 'े']




<div class="se-preview-section-delimiter"></div>

左から4,6番目はletterでなく,diacritic(発音記号)であって,結局grapheme clustersは

["न", "म", "स्", "ते"]




<div class="se-preview-section-delimiter"></div>

のようになる.このように,プログラマの直感とはかなり異なった方法でRustは文字列を保持していて,Stringのインデクシングを直感的に行えるようにするには,計算量が(O(1))では効かなくなる.インデクシングは常に定数計算量(O(1))であるべきと開発者が考えたため,Stringはインデクシングを実装していない.

Slicing Strings

Stringをbyteごとにスライスすることはできる.

let hello = "Здравствуйте"
let s = &hello[0..4];

sは最初から4番目までのByteへのrefereceで,&str型である.キリル文字は2bytes文字だから,sをプリントするとЗдとなる.
&hello[0..1]とすると,最初の1byteのみ保持するようになるはずだが,それぞれの文字の境界に両端がないスライシングをしようとすると実行時にPanicを起こす.

Methods for iterating Over Strings

Stringのそれぞれの文字にアクセスできる方法は他にもある.
Stringに含まれるUTF-8のscalar value(つまり文字)に何かを実行したいときは,chars methodを使うのが一番良い.“नमस्ते”にcharsを適用すると,もとの文字列を分解して,それぞれの文字をchar型にしてくれる.

for c in “नमस्ते”.chars() {
  println!("{}", c);
}

न
म
स
्
त
े

を返す.(発音記号ごとイテレーションしているがいいのか・・・・)
一方でbytes methodはそれぞれのbyteを返す.

for b in "नमस्ते".bytes() {
    println!("{}", b);
}

shell

224
164
// etc

Strings are Not so Simple

このように,Rustでの文字列の扱いはとても複雑なことがわかっただろう.

0 件のコメント:

コメントを投稿