Interpreting instructions :) from the compiler: missing Trait implementation for a custom error

Happy Friday everyone!

I'm having some difficulty interpreting the instructions from the compiler. It seems a little circular and in general having me think I need to adjust my thinking.

Here is the function triggering the error.

    pub fn chunks<'index>(&'index self, num: u8) -> Result<Chunks<'index>, StructureError> {
        let mut chunks = boundaries(self.record_cnt, num)
            .ok_or_else(|| Err(StructureError::InvalidState))?
            .iter()
            .map(|boundary| /* make a Chunk */)
            .collect::<Vec<Chunk>>();

        chunks[0] = /* update value */;

        Ok(chunks)
    }

// where, boundaries returns an Option
fn boundaries(task_size: u32, job_count: u8) -> Option<Vec<Boundary>> 

The error from the compiler:

error[E0277]: `?` couldn't convert the error to `StructureError`
  --> csv-simd/src/tape.rs:42:62
   |
42 |             .ok_or_else(|| Err(StructureError::InvalidState))?
   |                                                              ^ the trait `From<std::result::Result<_, StructureError>>` is not implemented for `StructureError`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: the following implementations were found:
             <StructureError as From<std::io::Error>>
   = note: required by `from`

The idea of

impl<T> From<std::result::Result<T,StructureError>> for StructureError {...}

...seems unusual if not possible.

From a top down, I believe I need to implement something like the following:

From<Error> for StructureError { 
  /* fn from that matches on the StructureError enum values*/
}

No? Thanks in advance to anyone with some advice for me.

- E

As you can see from the signature, the closure argument to Option::ok_or_else is supposed to return an E, not a Result<T, E>. If you change the line to

        .ok_or_else(|| StructureError::InvalidState)?

you should be good to go. (And in fact in this case you can just use .ok_or(StructureError::InvalidState)? because your closure doesn't have any side effects.)

1 Like

@cole-miller Thank you.

I was thinking the ? takes one of the Result instances constructed using Err(/* custom error */)... Which it does. That's where I got stumped.

I missed the fact that .ok_or_else and .ok_or each "augment" what I provide as input (F -> E or E) to produce the expected Result<T, E>... Cool.

Finally, the compiler's message was nonsensical. But, by definition of it always being right, was the product of my incorrectly wrapping StructureError with the Result construct. Without the extra layer the message from the compiler:

 the trait `From<std::result::Result<_, StructureError>>` is not implemented for `StructureError``

... reduces to:

the trait `From<StructureError>` ... for `StructureError`

Again, thank you.

2 Likes

Another way of thinking about it: if ok_or_else accepted any Result to be returned from its closure, it would be entirely possible to return an Ok(…), and then None.ok_or_else(|| Ok(…)) would be Ok.

However, that would be some seriously confusing API design, since it would conflict with the natural-language interpretation of "or" as "XOR". The name ok_or_else is probably read by most people as "Ok if some condition holds, Err otherwise– but this reading would no longer be true had the closure returnedResult` and not unconditionally the error type.

1 Like

Thank you for that. The analogy is useful. Not until just now did I formally consider how a function single binary enum -> single binary enum can be viewed in terms of a logic gate. A gate accepts two inputs. The missing piece was realizing how the body of the function not only specifies what type of gate (e.g., OR, AND, XOR etc.), but also models a fixed second input.

To reflect your point, ok_or_else where "Ok if some condition holds" means Ok -> Ok, and similarly NotOk -> NotOk. The logic and one of the inputs are fixed by the function. ok_or_else requires the user to specify the output value of NotOk. If it could be anything other than a "negative", the Ok -> Ok and NotOk -> [Ok] would defeat the purpose and expected outcome.

This is all that is required to see how Result is inherently an unstable/fault-prone input.

Not required, but just to finish the thought... We also know F -> E is only called when NotOk is the input. Following through on the above, assuming there is a hidden constant as the second input, the expected logic could be modeled as either

  • AND logic with one input = negative constant
  • OR logic with one input = positive constant

Per your @H2CO3 description of the "corrupted" version: it would behave like an XOR gate. As such, in either inputs from boundaries we generate a positive outcome (e.g., if I could have provided Ok(default value))

  • XOR logic with one input = negative constant such that
    Some (value), fixed negative -> Ok (value)
    None, fixed negative -> Ok (default value)

Wonderful. Thanks for that.