0

I use rust to open a file and parse its contents.

let file = fs::read_to_string(file_path).unwrap(); 

I then iterate over each line:

for item in file.lines() {
// processing
}

at this point item is of type &str. I now need only a slice of that so I:

let string_slice = &item[0..2];

however, string_slice is now of type &&str (two &s). I want to return a vec of strings, however the file I read doesnt need to live longer than the function scope I open it in. I can get to a string by calling to_string() twice; but this seems awkward.

let string_slice = &item[0..2].to_string();
return string_slice.to_string() //have to call twice

is there a more elegant way to pass ownership of these slices to a vec that I then return from the function?

david 11
  • 75
  • 8
  • In the description you say that the return type is `Vec`, but the code snippet contains `return string_slice.to_string()` which returns a single `String`. Which one do you want? – kotatsuyaki Feb 01 '23 at 09:58
  • it doesn't matter. in the end i want a string. the only difference would be return vec![string_slice.to_string()]. – david 11 Feb 01 '23 at 09:59
  • Also, can you clarify why you need to call `to_string` twice? The function [`std::string::ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string) returns a owned `String`, so I don't see the need to call it twice. – kotatsuyaki Feb 01 '23 at 10:00
  • 1
    The type of `string_slice` will be `&str`, not `&&str`. And even if it were `&&str`, a single call to `to_string()` would be enough to turn it into a `String`. – Sven Marnach Feb 01 '23 at 10:00
  • 1
    i found a better solution, which is to (&item[0..2]).to_string() -> returns a String. i think the order of operations without the () is different thats what caused me problems – david 11 Feb 01 '23 at 10:00
  • 2
    Or just `item[0..2].to_string()`. – Sven Marnach Feb 01 '23 at 10:01
  • ah ok so a String you need to index as &s[0..2] and a string slice &s you can index as s[0..2]? – david 11 Feb 01 '23 at 10:04
  • You can also truncate the string `item.truncate(2);` and then call `item.shrink_to_fit()` to free the extra memory but it probably won't be any faster than allocating a new string and freeing the old one completely because the new string is tiny in this case. – Dogbert Feb 01 '23 at 10:21

2 Answers2

3

The other answer explains what happens under the hood, but if you want to know the idiomatic way to achieve what you're trying to, this is it:

fn get_lines() -> Vec<String> {
    // Replace this with your actual input. You can `expect()` the value
    // (instead of `unwrap()`) if you're sure it's there or return a `Result`
    // if you're not.
    // You can also pass it in as an argument. In that case, the parameter type
    // should be `&str` instead of `String`. This is a common mistake among
    // beginners.
    let sample_input = String::from("one\ntwo\nthree\n");

    sample_input
        .lines()
        // You can use `to_string()`, `to_owned()`, or `into()` to convert a
        // string slice into a string. Either works. I find the automatic type
        // inference of `into()` most satisfying, but it's up to preference in
        // this case.
        // The type annotation for `line` is optional.
        .map(|line: &str| line[0..2].into())
        // Because the function returns a `Vec`, you can omit the type. I.e.,
        // you don't have to write `collect::<Vec<_>>()`. It's inferred.
        .collect()
}

Run this snippet on Rust Playground.

verified_tinker
  • 625
  • 5
  • 17
2
let string_slice: &str = &item[0..2];

however, string_slice is now of type &&str (two &s).

That's not right, the type of item[0..2] is str so you have to take a reference to use it since it's unsized, the type of &item[0..2] is then the &str you can work with.

When you directly invoke a method on it like here:

let string_slice: &String = &item[0..2].to_string();

due to auto (de-)referencing the compiler will add an automatic reference to the string slice so it desugars to this:

let string_slice: &String = &( // <- the referen you take explicitly
    /*the reference the compiler inserts -> */ (&item[0..2]).to_string()
);

I added an extra pair of parentheses to make the precedence clear. So after this string_slice which is a bit of a misnomer here is of type &String. To get a String instead you can just remove your manual reference.

let string_slice: String = item[0..2].to_string();

which desugars to

let string_slice: String = (&item[0..2]).to_string();

I've annotated all the types above for ease of understanding, but they're exactly what the compiler would infer for them.


As a tip to get the compiler to tell you what type a variable is if you don't have editor support which can tell you you can always add a type annotation of : () and the compiler will error with a message telling you what it expected the type to be:

let string_slice: () = &item[0..2];

will error with something like the following

error[E0308]: mismatched types
 --> src/main.rs:2:17
  |
  |     let string_slice: () = &item[..];
  |                       --   ^^^^^^^^^ expected `()`, found `&str`
  |                       |
  |                       expected due to this

For more information about this error, try `rustc --explain E0308`.
cafce25
  • 15,907
  • 4
  • 25
  • 31
  • thanks for your answer. it took me a while to understand the dereferencing of &item[0..2].to_string(), but im glad you explaned it. cheers – david 11 Feb 01 '23 at 11:14