Rust DISCUSSION

How does the ? operator work in Rust, and why does it fail with mixed error types?

Started by baha question mark operatorResult typeerror conversionFrom traitRust error handling
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

How does the ? operator work in Rust, and why does it fail with mixed error types?

baha Rust Forum
#1

My function reads a calibration file and parses a number: let s = std::fs::read_to_string(path)?; let gain: f32 = s.trim().parse()?;. With the return type Result<f32, std::io::Error> the first ? compiles, but the second complains that From<ParseFloatError> is not implemented for std::io::Error. Using ? in main gives a different error about the return type.

What does ? expand to, and what is the normal way to handle several error types in one function without writing match everywhere?

Community replies 4

Re: How does the ? operator work in Rust, and why does it fail with mixed error types?

#2

On a Result, expr? is roughly match expr { Ok(v) => v, Err(e) => return Err(From::from(e)) }. On success it unwraps the value. On failure it returns early from the enclosing function, converting the error into the function's declared error type through the From trait.

That conversion is what the message is about. read_to_string yields an io::Error, which matches your return type. parse::<f32>() yields a ParseFloatError, and no conversion from that into io::Error exists. The operator also works on Option: in a function returning Option, x? returns None early. The two cannot be mixed directly; bridge them with .ok_or(err)? or .ok()?.

Re: How does the ? operator work in Rust, and why does it fail with mixed error types?

#3

The quickest fix for applications and tools is a boxed trait object: fn load(path: &str) -> Result<f32, Box<dyn std::error::Error>>. The standard library provides a conversion into Box<dyn Error> for every type that implements Error, so both ? compile unchanged.

The same applies to main. The operator needs the enclosing function to return Result or Option, so declare fn main() -> Result<(), Box<dyn std::error::Error>> and end it with Ok(()). If main returns an Err, its Debug representation is printed and the process exits with a non-zero status.

Re: How does the ? operator work in Rust, and why does it fail with mixed error types?

#4

For library code, or anywhere the caller must tell failures apart, define an enum: enum LoadError { Io(std::io::Error), Parse(std::num::ParseFloatError) }, and implement From for each source type: impl From<std::io::Error> for LoadError { fn from(e: std::io::Error) -> Self { LoadError::Io(e) } }, and likewise for the parse error.

With both conversions in place the function body does not change; each ? picks the right one. Callers can then match on the variant, for example retrying on I/O errors and reporting a corrupt file on parse errors. The thiserror crate generates these impls from attributes, and anyhow is the popular equivalent of the boxed approach with context messages added.

Re: How does the ? operator work in Rust, and why does it fail with mixed error types?

#5

When a From impl is not wanted, convert at the call site with map_err: s.trim().parse::<f32>().map_err(|e| format!("bad gain value: {e}"))? in a function returning Result<f32, String>. That is also the place to add context such as the file name.

One pitfall: ? returns from the innermost function or closure. Inside .map(|l| l.parse::<f32>()?) it tries to return from the closure, and you get a type error. Collect fallible iterations as let vals: Result<Vec<f32>, _> = lines.iter().map(|l| l.parse::<f32>()).collect(); and apply ? to the result; collect stops at the first error. And do not swap ? for .unwrap() outside tests: it panics, which on an embedded target usually means a halt or reset.

TEP COMMUNITY