Avoid code for creating a Result if it will be discarded

I would like to achieve some kind of "conditional code" determined by the return type.
To be precise: If () is the return type, nothing should happen. If Result is the return type I want to do some checks.
Here is an example on how I tried it so far:

trait A {
    // I am sorry i called this dod, it would actually be "check" or similar.
    fn dod() -> Self;
}

impl A for () {
   // As described, do nothing for ()
    fn dod() -> Self { }
}

// impl A for Result... omitted

fn x<F: A>() -> F {
    F::dod()
}

fn main() {
    x();
    // This will work ofc:
    x::<()>();
}

Is there some way to make this or some similar "trick" work? Aka: Avoid validation if the result is discarded anyways in one location while doing the validation if it is for another location.

Can you elaborate a bit more? What did you try that doesn't work?

Depending on what you're doing, () will cause some code paths to be optimized away, just like it does for HashSet<K>, which uses HashMap<K, ()> internally. That's one of the advantages of monomorphism over polymorphism. The compiler creates a separate version of a generic struct for each used unique combination of its generic parameters. This allows every version to be optimized individually.

I'll expand the example here:

struct Thing;

trait A {
    fn check(thing: &Thing) -> Self;
}

impl A for () {
    fn check(thing: &Thing) -> Self { 
        // Do not check as the result will be ignored
    }
}

impl A for Result<bool,()> {
    fn check(thing: &Thing) -> Self {
        // Complex calculation to determine result
        Ok(true)
    }
}

fn x<F: A>() -> F {
    let thing = Thing;
    F::check(&thing)
}

fn main() -> Result<bool, ()> {
    // Run x() - but we don't care about a result, so it can be ()
    //x();
    
    //if x()? {
        // Now we care about the result, so it should be Result
    //}
    
    // This will work ofc:
    x::<()>();
    
    if x::<Result<bool, ()>>()? {
    }
    Ok(true)
}

Since the trait is only implemented for () and Result, and Result must be handled, the types of the commented calls "could" be inferred. They clearly are not.
But maybe there's another way to achieve this? Besides duplicating the functions or passing a discriminating parameter?

I need to clarify what I'm doing a bit better: I want to avoid to explicitely specify the types () and Result - if I do specify them it works, but the calller must provide more information that would optimally be needed.

Ie.: The trait A only has 2 impls, so my function returning it can only return one of those.

  • x(); must return (), because if it returned a Result, it would require it being handled (aka x()?;)
  • if x()? {...} must use Result, because of () cannot be evaluated in that context.

I guess that's asking a lot from the type inference system - but - my question is, could something similar be achieved in another way, without having fn x() and fn x_checked() or fn x(CheckedOrNot)?

fn main() {
    let _: Bar = foo();
    
    let val: Result<String, String> = foo();
    println!("{}",val.unwrap());
}

struct Bar;

fn foo<T: std::convert::From<Bar>>() -> T {
    Bar{}.into()
}

impl From<Bar> for Result<String,String>{
    fn from(_:Bar) -> Result<String,String> {
        Ok("Hellow from Bar".to_string())
    }
}

I got this far, couldn't get ? to work
fyi The struct Bar is needed only because I can't implement From ( ) for Result as I don't own either of them, otherwise I think we could replace Bar with ( )

Honestly this isn't all that helpful either, you're still specifying types

The type system does not require you to handle a Result.

1 Like

The compiler always treats a trait as an open set: some new implementation could be written or linked in at any time, and that shouldn’t break existing code. Thus, any logic that involves exhaustively listing all of the possibilities is unavailable to it.

Also, the must_use attribute is a suppressible warning. Because it’s legal to ignore a must_use value, the inference system can’t consider it in its decisions.


I would probably tackle this by returning a struct that has the check method. Code that wants to skip validation can ignore it, otherwise ? becomes .check()?. Once Try gets stabilized, the extra .check() can be removed. Untested example:

struct ThingValidator(Thing);

impl ThingValidator {
    fn check(self)->Result<(), String> { /* ... */ }
}

fn x()->ThingValidator {
    let thing = Thing;
    ThingValidator(thing)
}

With a little modification, it can also work for references:

struct ThingOpValidator<‘a>(&’a Thing);

impl Thing {
    pub fn op(&mut self)->ThingOpValidator<‘_> { /* ... */ }
}

True, but it will produce warning and the term unused_must_use's is more definitive. But I agree it's not "hard" enough to be used in inferrence.

It's a private trait, so no other crate should be able to build upon it.
But since not using a Result is just a warning, Rust can't tell what I want when I call x(); so my solution/request is flawed anyhow.

I agree with the struct::check but my example is simplified.
I do have 1 Thing, but there are a lot of mutators, each with a specific check.
It's also semantically reversed, it would first mutate the Thing and then check.

I know that could be fixed, but then it's back to the caller site explicitly giving types. Which would be similar to saying Thing::do + Thing::do_checked() -> Result<...>.

Although it's still a bit more verbose than I wanted (which is not possible I guess :joy: ) - I think the validator thing is nice, I'll extend it to

#[must_use]
struct ThingOp(CheckFn, PerformFn);

impl ThingOp {
  fn checked() -> Result<...>
  fn unchecked() 
}

or I will do the x_unchecked thing using macros. Thanks anyways!

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.