Skip to content

Instantly share code, notes, and snippets.

@patshaughnessy
Last active November 22, 2020 23:39
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save patshaughnessy/3252e2e718445d991499e5cd08e1949c to your computer and use it in GitHub Desktop.
Save patshaughnessy/3252e2e718445d991499e5cd08e1949c to your computer and use it in GitHub Desktop.
Rust Vec<String> find example
fn main() {
let needle = "list".to_string();
let haystack = ["some".to_string(), "long".to_string(), "list".to_string(), "of".to_string(), "strings".to_string()].to_vec();
if let Some(str) = haystack.iter().find(|&s| *s == needle) {
println!("{}", needle);
} else {
println!("Nothing there...");
}
}
@patshaughnessy
Copy link
Author

Is this the idiomatic way of finding a String object in a Vec? Am I missing something obvious? the closure syntax find(|&s| *s == needle) seems confusing to me.

@jabgibson
Copy link

jabgibson commented Mar 1, 2019

I don't have an answer for you, but I am also interested. I wish there was a watch button on these gist's. Being new to Rust I find your solution here very helpful whether its idiomatic or not.

@sts10
Copy link

sts10 commented Mar 1, 2019

In my search for an answer I found your Stack Overflow question, which seems to already have a pretty good answer.

A key line is "In your case .iter() iterates over Strings by reference, so it gives &String elements. Then .find() gives you access to each iterated the element by reference again, so you end up with a &&String argument." This is why you've got that |&s| *s == mess in your version — I agree that that syntax is confusing!

I'm still learning Rust, and coming from Ruby things like String vs &str and references in general still confuse me. But here's how I'd go about this.

I was able to avoid that weird syntax by using contains. Not sure if it's any more or less idiomatic of Rust (I'm still learning), but it seems tolerable?

fn main() {
    let needle: String = "list".to_string();
    let haystack: Vec<String> = vec!["some".to_string(), "long".to_string(), "list".to_string(), "of".to_string(), "strings".to_string()];

    if haystack.contains(&needle) {
        println!("{}", needle);
    } else {
        println!("not found");
    }
}

EDIT: Though as this tweet points out, into_iter helps simplify your original version a bit:

fn main() {
    let needle = "list".to_string();
    let haystack = vec!["some".to_string(), "long".to_string(), "list".to_string(), "of".to_string(), "strings".to_string()];

    if let Some(word) = haystack.into_iter().find(|s| s == &needle) {
        println!("{}", word);
    } else {
        println!("Nothing there...");
    }
}

(I'd avoid naming a variable str cuz that's just confusing.)

@joshtriplett
Copy link

contains is definitely the idiomatic approach. Also, you can avoid repeating .to_string(), and you can rely more on type inference:

fn main() {
    let needle = "list".into();
    let haystack: Vec<_> = vec!["some", "long", "list", "of", "strings"]
        .into_iter()
        .map(String::from)
        .collect();
    if haystack.contains(&needle) {
        println!("{}", needle);
    } else {
        println!("not found");
    }
}

@joshtriplett
Copy link

joshtriplett commented Mar 1, 2019

And if you're going to do membership tests often, consider a set rather than a vector.

@patshaughnessy
Copy link
Author

Thanks all of you for the ideas!

Yup as I learned on StackOverflow, using contains seems a lot simpler and cleaner in this example. Using into_iter will also work, but isn't what I need in my app because I don't want to move the data out.

And thanks for the type inference tip - yes much cleaner.

The deeper issue for me is that Rust's & and && syntax can be very confusing, especially with iterators and closures. Will just take some getting used to I suppose :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment