Doing the read I get a Vec<&str> that, as I understand it, is owned by csv:
fn from_csv<P: AsRef<Path>>(path: P) -> Result<impl Table + 'a, IOError> {
let mut csv = Reader::from_path(path)?;
// get the headers from the CSV file
let headers :Vec<&str> = csv.headers()?.into_iter().collect::<Vec<_>>();
let columns = headers.iter().map(|h| &**h).collect::<Vec<_>>(); // doesn't work
<snip>
RowTable{ columns, rows }
}
I'd like to construct a Vec<&'a str> so that the lifetime of those &str matches my Table. Is there any way to copy/clone the &str away from csv such that I can then have it owned by my eventual returned value? The error I currently get is:
31 | impl <'a> RowTable<'a> {
| -- lifetime `'a` defined here
...
50 | let headers = csv.headers()?.into_iter().collect::<Vec<_>>();
| --- first mutable borrow occurs here
...
71 | / Ok(RowTable {
72 | | columns,
73 | | rows
74 | | })
| |__________- returning this value requires that `csv` is borrowed for `'a`
Related: as a general rule-of-thumb, is it simply best to store Vec<String> instead of Vec<&'a str>? Thanks!
General rule-of-thumb: Storing references into struct fields or collection is hard. It's better to avoid them until you feel you mastered the basic language constructs. Vec<String> -> Vec<&'a str> should considered as optimization and usually it's not worth optimizing before you have working code and profiling setup.
@Hyeonu, OK. I started switching from Vec<String> to Vec<&'a str> simply because I have methods that do things like look for a column name. I was under the impression it's always best to take a &str for these methods. However, then I always end up converting them to String inside my methods. Maybe this isn't the end-of-the-world.
Really just looking for the idiomatic way to do this... thanks!
If the function can easily take a String or &str and operate exactly the same way with either, then yes a &str argument is better because it effectively accepts both; converting a String to a &str is trivial but the reverse is potentially costly. I'm guessing that's why you got that impression.
But that's generally only true for functions that just take the string as an argument, look at it a bit, and then don't need it anymore. Once you start putting the strings in other structs or building up collections of them, you're definitely not writing code that can "operate exactly the same way" with String or &str.
It's not the end of the world, but it is a pretty strong code smell.
If you always i.e. unconditionally convert a &str argument to a String, then you probably should just take a String argument. It gives the caller a more accurate impression of the cost of calling the function, and provides them with the option to give up ownership of an existing String (in which case no one has to make the deep copy).
If you sometimes need to do the deep copy to make a new String, but not always, then it might be worth having a &str argument to ensure you have the option to skip the deep copy. Or a Cow. Or some other more complex maybe-owned-maybe-not structure. But this is exactly the sort of optimization you should not worry about on your first pass, unless you're already very experienced. In general, you won't be able to tell if this is a viable or worthwhile optimization until you've got the whole architecture of the program figured out and written a basic end-to-end test suite.
In this case it's less about idioms and more about what ownership you really want to express. But as the others said: when in doubt, prefer owned values, except when you're confident that a quick borrow is all you need. Owned values are inherently a lot simpler to reason about (both for you and the borrow checker).