Iterator map() needs FromResidual trait but for loop works

Can someone help me understand (or point to a topic that does) why the for loop works but the map does not?

This is my struct that implements TryFrom:

#[derive(Serialize, Deserialize)]
pub struct QualifiedValue<T> {
    value: T,
    t_stamp: NaiveDateTime,
    qual: i64,
    tag_name: String
}

impl TryFrom<&Row> for QualifiedValue<f64> {
    type Error = pError;

    fn try_from(row: &Row) -> Result<Self, Self::Error> {
        Ok(Self {
            value: row.try_get("float_val")?,
            t_stamp: row.try_get("t_stamp")?,
            qual: row.try_get("qual")?,
            tag_name: row.try_get("tag_name")?
        })
    }
}

This function will not compile:

pub async fn get_hist_for(&self, tag_name: &str) -> Result<Vec<QualifiedValue<f64>>, Box< dyn Error>> {
        let rows = self.client
            .query(&self.statements.hist_flt, &[&tag_name])
            .await?;
        Ok(rows.iter().map(|r| QualifiedValue::try_from(r)?).collect())
    }

But this one will:

    pub async fn get_hist_for(&self, tag_name: &str) -> Result<Vec<QualifiedValue<f64>>, Box< dyn Error>> {
        let rows = self.client
            .query(&self.statements.hist_flt, &[&tag_name])
            .await?;
        
        let mut qvs = Vec::with_capacity(rows.len());
        for r in rows {
            let qv = QualifiedValue::try_from(&r)?;
            qvs.push(qv);
        }
        Ok(qvs)
    }

This is the compiler error I get:

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
   --> src/migr/qdb.rs:117:59
    |
117 |         Ok(rows.iter().map(|r| QualifiedValue::try_from(r)?).collect())
    |                            ---                            ^ cannot use the `?` operator in a closure that returns `QualifiedValue<_>`
    |                            |
    |                            this function should return `Result` or `Option` to accept `?`
    |
    = help: the trait `FromResidual<Result<Infallible, _>>` is not implemented for `QualifiedValue<_>`

Replacing the ? with .unwrap() lets the first function compile. But I'm trying to understand what the exact difference is between map() and just the for loop. Do I have to implement the Try trait for my struct as well? If so, why don't I have to for the for loop?

No. You can probably just remove the outer Ok(...) and the ? in your map and type inference will do the rest:

pub async fn get_hist_for(&self, tag_name: &str) -> Result<Vec<QualifiedValue<f64>>, Box< dyn Error>> {
    let rows = self.client
        .query(&self.statements.hist_flt, &[&tag_name])
        .await?;
   rows.iter().map(|r| QualifiedValue::try_from(r)).collect()
}

Or if that doesn't work because errors are not implicitly coerced, something like this:

pub async fn get_hist_for(&self, tag_name: &str) -> Result<Vec<QualifiedValue<f64>>, Box< dyn Error>> {
    let rows = self.client
        .query(&self.statements.hist_flt, &[&tag_name])
        .await?;
   rows.iter().map(|r| Ok(QualifiedValue::try_from(r)?)).collect()
}

You can read more about the ? (Try) operator in the book:

1 Like

The thing? operation is similar to:[1]

match thing {
    Ok(succcess) => success,
    Err(err) => return Err(err.into()),
}

That works fine in the second case:

        for r in rows {
            let qv = QualifiedValue::try_from(&r)?;
            qvs.push(qv);
        }

Because the ? is immediately inside a function that returns Result.[2]

Whereas here:

Ok(rows.iter().map(|r| QualifiedValue::try_from(r)?).collect())
//                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The ? is immediately in a closure that returns a QualifiedValue. The ? doesn't "see through" the closure all the way to the enclosing function. This is similar to how return or break do not see outside of a closure, when they're in a closure -- which can also lead to restructuring iterator chains into a for to remove the closure, like you have done.

(The error is phrased the way it is because of the actual desugaring of thing?, which involves the unstable Try and FromResidual traits.)


  1. the actual desugaring is more complicated but this will do for illustrative purposes ↩︎

  2. Technically an async function, blah blah, but again close enough ↩︎

5 Likes

That was very helpful. Thank You!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.