Question about function argument convention regarding &Vec<String> vs &[&str]

I've heard that fn x(argument:&str) is recommended over fn x(argument:&String) because rust can coerce strings into &str

And that it can do the same for &Vec -> &

But when I try to combine them I get an error. I get that I'm trying to coerce String into &str (rather than &String into &str) which isn't allowed, but what's the recommended way of handling this in a way that maximizes genericity? Is there a way to tell rust I want to borrow what's inside the vector, not own it? I looked at the std::vec::Vec documentation and I don't see a .as_borrowed() method.

If Rust had function overloading I could just make a constructor that would borrow the innards and then pass it along, but it doesn't. Is there a String trait I can use to do this with generics?

fn main() {
    let vector: Vec<String> = vec!["a".to_string(),"b".to_string(),"c".to_string()];
    test(&vector);
}

fn test(something:&[&str]){
    println!("{:?}",something);
}

Playground link for the lazy: Rust Playground

2 Likes

This applies only to the outermost type. Vec<String>&[String]

The type can't be coerced recursively, because each element of [String] takes 3 usize, and each element of [&str] takes 2 usize, so they're physically incompatible in memory.

But this works, so it can definitely handle that aspect

fn main() {
    let string: String = "a".to_string();
    test(&string);
}

fn test(something:&str){
    println!("{:?}",something);
}

It works for one String, because the first two fields of String are same as str, so it's possible to give pointer to String and have user of str ignore the extra data after it. It doesn't work for arrays, because the data after it is interpreted as the next element.

If you really need this flexibility, then taking impl IntoIterator<Item=impl AsRef<str>> is the most universal (you will have to call into_iter() and as_ref()).

You can use generics for it:

fn foo<I: IntoIterator<Item=T>, T: AsRef<str>>(data: I) { .. }

This function will work with both &[&str] and Vec<String>.

UPD: A bit too late. :slight_smile:

2 Likes

It's not so much about need as best practice. It seems akward to make people who want to pass you strings build a pre-iterator just to improve genericity.

fn main() {
    let vector: Vec<String> = vec!["a".to_string(),"b".to_string(),"c".to_string()];
    
    // === preprocessor ===
    // it's akward to make someone do this who wants to use your crate
    let mut preprocessed: Vec<&str> = Vec::new();
    for string in vector.iter(){
        preprocessed.push(string);
    }
    // === preprocessor ===
    
    test(&preprocessed);
}

fn test(something:&[&str]){
    println!("{:?}",something);
}

I don’t think placement of fields inside String is relevant. &str is a (manufactured) fat ptr whose data and len components could’ve come from anywhere, they don’t need to point at contiguous data (the len isn’t a ptr anyway, just a usize).

The short version is vector.iter().map(|s| s.as_str()).collect().

It's simply physically impossible to use [String] as [&str] without copying. ABCABCABC data can't be used where ABABAB is expected. That's regardless of the language. In C it wouldn't work either.

1 Like

These parts I get

vector
.iter()
.as_str()
.collect()

Where in the rust book do I find .map(|s|...)? I've seen it used around but I'm on chapter 12 and haven't seen it mentioned yet

Also, as been mentioned, you can use generics to avoid collecting into a Vec if that’s not an essential part of what you need.

Thanks though that doesn't explain the (|x| abc) syntax

That’s closure syntax - it’s in the book (I’m on mobile or else I’d find it for you but you can probably do that).

Fundamentally I just want to make it as easy as possible for other people to use my functions and to maximize their genericity. letting fn x(a:&str) take in an &String and an &str seems reasonable and useful, but it seems difficult to follow that same pattern into the contents of Vecs / Arrays.

is this it here?
https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html?highlight=closure,syntax#closure-type-inference-and-annotation

The generic code above should make it easy and ergonomic for callers of your function (don’t let the longish generic signature scare you).

Yes, that whole chapter (not just the type inference/annotation section).

Ok, I'll look that over.

The trait specification signature in generics still throws me off, I need to get better at reading/using it

If you control the method you're calling, there's always the option of something like

fn test(something: &[impl AsRef<str>])
2 Likes

This seems to get it to accept them as parameters, but I'm having trouble accessing the data within. Can you help fix this playground code?

Here is the fixed variant. If you want to print the whole slice you need to add Debug bound. (btw with it you can print thing directly without conversion) Plus before converting to String first you have to explicitly get &str via as_ref. (Or you could use Deref bound instead of AsRef if you want to utilize automatic coercion)