質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

1557閲覧

RustでStringに文字列を二個以上追記したいがミュータブルな参照を二つ作れないためエラーになる。

tasuren

総合スコア76

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

2クリップ

投稿2021/10/10 06:43

rust

1pub fn replace(mut text: String, from: usize, to: usize) -> String { 2 text = text[..from].to_string(); 3 text.push_str(&mut text[from..from + 1]); 4 text.push_str(&mut text[to..]); 5 text 6}

上記の渡された文字列textfrom番目にある文字をto番目と置き換える関数を作っているんですが以下のようなエラーが出て困っています。

md

1cannot borrow `text` as mutable more than once at a time 2second mutable borrow occurs here

二つミュータブルな参照を作ることはできないのはわかるのですが&mut text[ ...&text[ ...にするとミュータブルとして扱えないというようなエラーが出てしまいます。
どうすればよいでしょうか?
誰か教えてほしいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

引数は参照専用にして、置き換え結果は返り値として得るのが簡便です。なお、参照にはStringではなく&strを使う方が、関数の応用範囲が広がるため好ましいです。

どうしても、引数をインラインで変更したければ、関数内で一度tmp変数に文字列をコピーして、それを参照しつつ、引数のtextをゼロから組み立てるべきでしょう。

Rust

1pub fn replace(text: &str, from: usize, to: usize) -> String { 2 let mut res = text[..from].to_string(); 3 res.push_str(&text[to..to + 1]); 4 res.push_str(&text[from + 1..to]); 5 res.push_str(&text[from..from + 1]); 6 res.push_str(&text[to + 1..]); 7 res 8} 9 10fn main() { 11 let s = replace("abcdefg", 2, 5); 12 println!("{}", s); //abfdecg 13}

投稿2021/10/10 08:43

編集2021/10/10 08:44
toast-uz

総合スコア3266

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Stringに文字列を二個以上追記したい

上の質問の直接の答えではないのですが、「渡された文字列textfrom番目にある文字をto番目と置き換える」のなら、slice型のswapメソッドを使うこともできます。

また、ご質問のようなスライスを使用したコードですと、文字列が英数文字だけのときは正しく動きますが、平仮名や漢字のような文字が含まれているとpanicするという問題があります。

まず、panicについて説明して、次にswapメソッドについて説明します。

String&strでは内部データー表現としてUTF-8エンコーディングを使用しています。UTF-8では、たとえば"a"[0x61_u8]の1バイトで表現されますが、"あ"[0xE3_u8, 0x81, 0x82]の3バイトで表現されます。そして、&text[..from]のように&strからスライスを作るときは、インデックスとして「何文字目」ではなくて、「UTF-8バイト列の何バイト目」という意味になります。もし開始・終了インデックスのどちらかがUTF-8の文字の境目から外れているとpanicします。

rust

1// panicするコードの例 2fn main() { 3 // "あ" は [0xE3_u8, 0x81, 0x82] の3バイト 4 let text = "あいう".to_string(); 5 // 0バイト目だけを含むスライスを作成する 6 let mut res = &text[..1].to_string(); 7}

console

1thread 'main' panicked at 'byte index 1 is not a char boundary; 2it is inside 'あ' (bytes 0..3) of `あいう`'

もしreplace関数が英数字だけを扱うのなら、ご質問のコードのようなやり方で問題ありません。しかし、それ以外の文字も扱う必要があるのなら別の方法を使うことになります。

いちばん確実な方法は&strをいったんVec<char>に変換することです。これにより、スライスのインデックス指定が、UTF-8の何バイト目ではなくて、何文字目(何個目のchar)という意味になります。

rust

1fn main() { 2 let text = "あいう".to_string(); 3 // &strからVec<char>へ変換する 4 let mut chars: Vec<_> = text.chars().collect(); 5 // Vec<char>のスライスからStringへ変換する 6 let mut res: String = chars[..1].iter().collect(); 7 println!("{}", res); // あ 8}

ただし、この方法ですと&strからVec<char>への変換とVec<char>からStringへの変換が必要になるので、実行時の効率は良くありません。

&str::charの代わりに&str::char_indicesを使うともっと効率がよくなりますが、コードは少し複雑になります。(コード例は省略します)

次にswapメソッドについて説明します。

slice型のswapメソッドは可変スライスのfrom番目にある要素をto番目と置き換えます。(ドキュメント

これをchars()とあわせて使うと、replaceの実装は以下のようになります。

rust

1pub fn replace(text: &str, from: usize, to: usize) -> String { 2 // &str型からVec<char>型へ変換する 3 let mut chars: Vec<_> = text.chars().collect(); 4 // from番目の文字とto番目を交換する 5 chars.swap(from, to); 6 // Vec<char>型からStringへ変換する 7 chars.into_iter().collect() 8} 9 10fn main() { 11 let s = replace("あいう", 0, 2); 12 assert_eq!(s, "ういあ"); 13}

また、もしreplace関数が英数字だけを扱うのなら、asciiクレートを使うのがおすすめです。

rust

1// Cargo.tomlに以下を追加する 2// [dependencies] 3// ascii = "1.0" 4 5use ascii::{AsAsciiStr, AsciiStr, AsciiString}; 6 7// &AsciiStr型は&str型に相当するが、文字列が1バイトのASCIIコードで表される 8// 英数字記号だけで構成されていることを保証する 9// AsciiString型はString型に相当する 10pub fn replace(text: &AsciiStr, from: usize, to: usize) -> AsciiString { 11 // &AsciiStr型からAsciiString型へ変換する 12 let mut res = AsciiString::from(text); 13 // 可変スライスを取得しswapで交換する 14 res.as_mut_slice().swap(from, to); 15 res 16} 17 18fn main() { 19 // s0は&AsciiStr型。"abcdefg"は全て1バイトのASCIIコードで表せるので 20 // as_ascii_strは成功する。 21 let s0 = "abcdefg".as_ascii_str().unwrap(); 22 let s = replace(s0, 2, 5); 23 assert_eq!(s, "abfdecg"); 24 25 // "あいう"は1バイトのASCIIコードで表せないので&AsciiStrを作ろうとすると 26 // エラーになる 27 assert!("あいう".as_ascii_str().is_err()); 28}

投稿2021/10/10 11:37

tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tasuren

2021/10/10 13:49

おお、こういうのもあるんですね。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問