Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Edit string in place with a function

Tags:

rust

lifetime

I am trying to edit a string in place by passing it to mutate(), see below.

Simplified example:

fn mutate(string: &mut &str) -> &str {
    string[0] = 'a'; // mutate string
    string
}

fn do_something(string: &str) {
    println!("{}", string);
}

fn main() {
    let string = "Hello, world!";
    loop {
        string = mutate(&mut string);
        do_something(string);
    }
}

But I get the following compilation error:

main.rs:1:33: 1:37 error: missing lifetime specifier [E0106]
main.rs:1 fn mutate(string: &mut &str) -> &str {
                                          ^~~~
main.rs:1:33: 1:37 help: this function's return type contains a borrowed value, but the signature does not say which one of `string`'s 2 elided lifetimes it is borrowed from
main.rs:1 fn mutate(string: &mut &str) -> &str {
                                          ^~~~

Why do I get this error and how can I achieve what I want?

like image 619
Semi Avatar asked Dec 05 '14 14:12

Semi


People also ask

Can you modify a string in place in Python?

Strings in Python are immutable. Therefore, we cannot modify strings in place.

How do you change a string to a function?

To convert a string in to function "eval()" method should be used. This method takes a string as a parameter and converts it into a function.

Can we modify strings in place in C ++?

Yes, no problem at all. If you really want a reference, section 21.4. 5/2 of the C++11 standard.


1 Answers

You can't change a string slice at all. &mut &str is not an appropriate type anyway, because it literally is a mutable pointer to an immutable slice. And all string slices are immutable.

In Rust strings are valid UTF-8 sequences, and UTF-8 is a variable-width encoding. Consequently, in general changing a character may change the length of the string in bytes. This can't be done with slices (because they always have fixed length) and it may cause reallocation for owned strings. Moreover, in 99% of cases changing a character inside a string is not what you really want.

In order to do what you want with unicode code points you need to do something like this:

fn replace_char_at(s: &str, idx: uint, c: char) -> String {
    let mut r = String::with_capacity(s.len());
    for (i, d) in s.char_indices() {
        r.push(if i == idx { c } else { d });
    }
    r
}

However, this has O(n) efficiency because it has to iterate through the original slice, and it also won't work correctly with complex characters - it may replace a letter but leave an accent or vice versa.

More correct way for text processing is to iterate through grapheme clusters, it will take diacritics and other similar things correctly (mostly):

fn replace_grapheme_at(s: &str, idx: uint, c: &str) -> String {
    let mut r = String::with_capacity(s.len());
    for (i, g) in s.grapheme_indices(true) {
        r.push_str(if i == idx { c } else { g });
    }
    r
}

There is also some support for pure ASCII strings in std::ascii module, but it is likely to be reformed soon. Anyway, that's how it could be used:

fn replace_ascii_char_at(s: String, idx: uint, c: char) -> String {
    let mut ascii_s = s.into_ascii();
    ascii_s[idx] = c.to_ascii();
    String::from_utf8(ascii_s.into_bytes()).unwrap()
}

It will panic if either s contains non-ASCII characters or c is not an ASCII character.

like image 151
Vladimir Matveev Avatar answered Sep 29 '22 23:09

Vladimir Matveev