Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I change characters at a specific index within a string in rust?

Tags:

string

char

rust

I am trying to change a single character at a specific index in a string, but I do not know how to in rust. For example, how would I change the 4th character in "hello world" to 'x', so that it would be "helxo world"?

like image 512
rust-coder100 Avatar asked Sep 13 '25 17:09

rust-coder100


2 Answers

The easiest way is to use the replace_range() method like this:

let mut hello = String::from("hello world");
hello.replace_range(3..4,"x");
println!("hello: {}", hello);

Output: hello: helxo world (Playground)

Please note that this will panic if the range to be replaced does not start and end on UTF-8 codepoint boundaries. E.g. this will panic:

let mut hello2 = String::from("hell😀 world");
hello2.replace_range(4..5,"x"); // panics because 😀 needs more than one byte in UTF-8

If you want to replace the nth UTF-8 code point, you have to do something like this:

pub fn main() {
    let mut hello = String::from("hell😀 world");
    hello.replace_range(
        hello
            .char_indices()
            .nth(4)
            .map(|(pos, ch)| (pos..pos + ch.len_utf8()))
            .unwrap(),
        "x",
    );
    println!("hello: {}", hello);
}

(Playground)

like image 130
HHK Avatar answered Sep 15 '25 13:09

HHK


The standard way of representing a string in Rust is as a contiguous range of bytes encoded as a UTF-8 string. UTF-8 codepoints can be from one to 4 bytes long, so generally you can't simply replace one UTF-8 codepoint with another because the length might change. You also can't do simple pointer arithmetic to index into a Rust String to the nth character, because again codepoint encodings can be from 1 to 4 bytes long.

So one safe but slow way to do it would be like this, iterating through the characters of the source string, replacing the one you want, then creating a new string:

fn replace_nth_char(s: &str, idx: usize, newchar: char) -> String {
    s.chars().enumerate().map(|(i,c)| if i == idx { newchar } else { c }).collect()
}

But we can do it in O(1) if we manually make sure the old and new character are single-byte ascii.

fn replace_nth_char_safe(s: &str, idx: usize, newchar: char) -> String {
    s.chars().enumerate().map(|(i,c)| if i == idx { newchar } else { c }).collect()
}

fn replace_nth_char_ascii(s: &mut str, idx: usize, newchar: char) {
    let s_bytes: &mut [u8] = unsafe { s.as_bytes_mut() };
    assert!(idx < s_bytes.len());
    assert!(s_bytes[idx].is_ascii());
    assert!(newchar.is_ascii());
    // we've made sure this is safe.
    s_bytes[idx] = newchar as u8;
}
fn main() {
    let s = replace_nth_char_safe("Hello, world!", 3, 'x');
    assert_eq!(s, "Helxo, world!");
    
    let mut s = String::from("Hello, world!");
    replace_nth_char_ascii(&mut s, 3, 'x');
    assert_eq!(s, "Helxo, world!");
}

Keep in mind that idx parameter in replace_nth_char_ascii is not a character index, but instead a byte index. If there are any multibyte characters earlier in the string, then the byte index and the character index will not correspond.

like image 44
NovaDenizen Avatar answered Sep 15 '25 12:09

NovaDenizen