Is there an elegant way to handle None with a type that cannot be inferred?

⚓ Rust    📅 2026-06-19    👤 surdeus    👁️ 3      

surdeus

I'm trying to write a mildly flexible library function API that accepts an optional event handler parameter (currently an rxrust Observer, but I'm not married to that) -- so far without much luck.
Behold her glory:

pub async fn my_fantabulous_fn(
    opt_progress_observer: &mut Option<impl Observer<f64, std::convert::Infallible>>,
) -> anyhow::Result<()> {...}

The Observer expects floats that represent percentage progress events towards some goal. The calling client goes something like:

let task_progress_observer = Shared::subject();
let task_progress_subscription =
    task_progress_observer.clone().subscribe(move |v: f64| {
        tracing::info!(
            "Progress: {:.2}",
            v
        );
    });
let result = match my_fantabulous_fn(
    &mut Some(task_progress_observer),
)
.await {...}

Which works great! BUT what about a calling client who doesn't care about getting progress reports? Well...

let result = match my_fantabulous_fn(
    &mut None,
)

Seems like it should work, but fails with the error:

let result = my_fantabulous_fn(
     |                          -------------------- required by a bound introduced by this call
...
1759 |                 &mut None
     |                      ^^^^ cannot infer type of the type parameter `T` declared on the enum `Option`
     |
     = note: cannot satisfy `_: rxrust::Observer<f64, Infallible>`
     = help: ...
note: required by a bound in `my_fantabulous_fn`
    --> src\...
     |
1698 | ...pub async fn my_fantabulous_fn(
     |                 -------------------- required by a bound in this function
1699 | ...opt_progress_observer: &mut Option<impl Observer<f64, std::convert::Infallible>>) -> anyhow::Resu...
     |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `my_fantabulous_fn`
help: consider specifying the generic argument
     |
1759 |                &mut None::<T>
                               +++++

I tried shoving the exact impl trait type signature into None as None::<impl Observer<f64, std::convert::Infallible>>, but that syntax doesn't compile. Trying dyn trait complains that my_fantabulous_fn doesn't want dyn anything, which brings us back to the dyn trait problem for the same API where I don't want to impose an exact type signature on callers.

The best 'solution' I can think of is to make a 'null Observer' type in the library that clients can use to satisfy Option::None's generic:

pub struct NoneObserver {}
impl Observer<f64, std::convert::Infallible> for NoneObserver {
    fn next(&mut self, _: f64) {}
    fn error(self, _: std::convert::Infallible) {}
    fn complete(self) {}
    fn is_closed(&self) -> bool {
        true
    }
}

and then the calling client can do:

let result = match my_fantabulous_fn(
    &mut None::<NoneObserver>,
)

However, that is unintuitive and awful. None should be synonymous with nullptr in my mind; it shouldn't need to know what it isn't! It also should be size 0 imo, but I gather it winds up with a size derived from the inferred type it could be but isn't. Is there any better way to make an Option wrap a 'placeholder' concrete type such as impl trait?

I can't have a concrete type with fully implemented behavior as the my_fantabulous_fn() param since that's a library function and has no idea what the calling client will need to do with progress report data, but the library function obviously needs to know what the input type will be so any concrete type would need to be implemented in the library crate... and we go 'round in design circles trying not to look at impl trait because of the None:: problem or dyn trait because of the strict type signature problem. Meanwhile, the Orphan Rule prevents the client from overriding the implementation of a function in a type owned by the library crate, so we can't get cute with behavior injection on the client side.

I could maybe do a concrete wrapper type that has a function that accepts a closure or something and otherwise does nothing, and use that to satisfy None's type inference, but that feels both ugly AND overcomplicated.

Am I missing any options for a better API design? I don't think I've come across any stdlib APIs that required me to manually specify the type of a None value, so I feel like I'm in antipattern territory. Maybe not, though?

1 post - 1 participant

Read full topic

🏷️ Rust_feed