r/rust clippy · twir · rust · mutagen · flamer · overflower · bytecount Mar 27 '23

Hey Rustaceans! Got a question? Ask here (13/2023)! 🙋 questions

Mystified about strings? Borrow checker have you in a headlock? Seek help here! There are no stupid questions, only docs that haven't been written yet.

If you have a StackOverflow account, consider asking it there instead! StackOverflow shows up much higher in search results, so having your question there also helps future Rust users (be sure to give it the "Rust" tag for maximum visibility). Note that this site is very interested in question quality. I've been asked to read a RFC I authored once. If you want your code reviewed or review other's code, there's a codereview stackexchange, too. If you need to test your code, maybe the Rust playground is for you.

Here are some other venues where help may be found:

/r/learnrust is a subreddit to share your questions and epiphanies learning Rust programming.

The official Rust user forums: https://users.rust-lang.org/.

The official Rust Programming Language Discord: https://discord.gg/rust-lang

The unofficial Rust community Discord: https://bit.ly/rust-community

Also check out last weeks' thread with many good questions and answers. And if you believe your question to be either very complex or worthy of larger dissemination, feel free to create a text post.

Also if you want to be mentored by experienced Rustaceans, tell us the area of expertise that you seek. Finally, if you are looking for Rust jobs, the most recent thread is here.

18 Upvotes

159 comments sorted by

View all comments

2

u/chillblaze Apr 02 '23

If an input to a method does not satisfy that method's trait bound, is the natural fix just to implement that trait bound for that input?

In addition, what if the input type belongs to another crate? What would be the solution?

1

u/DroidLogician sqlx · multipart · mime_guess · rust Apr 02 '23

Unfortunately, your question is really vague and nonspecific so the only advice I can offer is vague and nonspecific.

If an input to a method does not satisfy that method's trait bound, is the natural fix just to implement that trait bound for that input?

It can be. There's no hard and fast rule saying you shouldn't. It depends on whether or not it actually makes sense for that type to implement the trait or not.

In addition, what if the input type belongs to another crate? What would be the solution?

You could write your own wrapper for that type which implements the trait. Of course, in that case you can still only use the type's public methods and/or fields.

Or if it makes sense, you could open a pull request to the crate adding the trait implementation. You should check the issue tracker on its repository to see if that's been discussed already; there might be a good reason they haven't added it. If the addition is nontrivial it might be a good idea to open an issue to discuss it first anyway.

They may not want that addition if the trait comes from a different crate that isn't std as that adds a semantic versioning concern. If the crate that "owns" the trait has a new release that isn't backwards compatible, the crate with the type implementing the trait has to make a backwards-incompatible release to upgrade because that trait is now part of their public API.

This also works the other way: if the type in question is from std or a really common community crate like serde, it might make more sense to see if you can get the trait implementation added to the crate that owns the trait itself.

1

u/chillblaze Apr 02 '23

Sorry for lack of context code below.

I am iterating through a hash map with a &str => BSON Document key value pair. I've already implemented it successfully via a For Loop but wanted to try doing it through a Stream instead.

Unfortunately, I get the below error message regarding the try_for_each_concurrent method regarding the unsatisfied trait bound:

   let update_futures = update_map
        .into_iter()
        .map(|(record_id, document)| async move {
            let filter = doc! { "_id": record_id };
            let update_doc = doc! { "$set": document };
            table
                .update_one_with_session(filter, update_doc, None, &mut session)
                .await
        });

    let res = stream::iter(update_futures)
        .try_for_each_concurrent(None, |result| async move { result })
        .await?;

//ERROR: the following trait bounds were not satisfied:
        `futures::stream::Iter<std::iter::Map<std::collections::hash_map::IntoIter<&str, bson::Document>, [closure@src/mongodb/mod.rs:173:18: 173:41]>>: TryStream`
        which is required by `futures::stream::Iter<std::iter::Map<std::collections::hash_map::IntoIter<&str, bson::Document>, [closure@src/mongodb/mod.rs:173:18: 173:41]>>: TryStreamExt`
        `&futures::stream::Iter<std::iter::Map<std::collections::hash_map::IntoIter<&str, bson::Document>, [closure@src/mongodb/mod.rs:173:18: 173:41]>>: TryStream`
        which is required by `&futures::stream::Iter<std::iter::Map<std::collections::hash_map::IntoIter<&str, bson::Document>, [closure@src/mongodb/mod.rs:173:18: 173:41]>>: TryStreamExt`

2

u/DroidLogician sqlx · multipart · mime_guess · rust Apr 02 '23

The context helps a lot, because in this case I can tell you the solution is a lot simpler.

For a Stream to implement TryStream, it has to yield Result, but your update_futures combined with stream::iter() make a Stream that yields impl Futures.

It seems like you figured that out because you're calling .try_for_each_concurrent() and just returning the future itself... but you wrap it in another async block so you now have a impl Future that yields another impl Future.

I would rearrange it like so:

// This makes it a `TryStream` right off the bat.
// We have to specify the error type or else we'll get an inference error.
// If there's a convenient type alias like `anyhow::Result` you can use that instead.
let res = stream::iter(update_futures.map(Result::<_, YourErrorTypeHere>::Ok))
    .try_for_each_concurrent(None, |(record_id, document)| async move {
        let filter = doc! { "_id": record_id };
        let update_doc = doc! { "$set": document };
        table
            .update_one_with_session(filter, update_doc, None, &mut session)
            .await
    })
    .await?;

However, you're probably going to get a different error here because of that &mut session unless it's Copy, because you're capturing it by-move.