Optionally supporting an operation in a Trait

โš“ Rust    ๐Ÿ“… 2025-07-30    ๐Ÿ‘ค surdeus    ๐Ÿ‘๏ธ 10      

surdeus

Warning

This post was published 126 days ago. The information described in this article may have changed.

I was just tinkering with a trait that has two methods:

  1. The first method encodes whether or not some operation is supported. It is cheap to call.
  2. If the operation is supported, we should be able to call the second method at a later point in time (some checks happen when the operation is supported, and before actually executing the operation). I wanted to avoid having an Option return type for this second method, since we should already know from the first method whether the operation is supported or not, and will only be calling this second method if the operation is supported.

Using dependent types, we could just change the return type based on the value returned by the first method. Since that is not a possibility here, I tried to encode something similar by using Infallible:

enum IsSupported<Token> {
    NotSupported,
    Supported(Token)
}

impl<Token: Copy> IsSupported<Token> {
    fn unwrap(&self) -> Token {
        use IsSupported::*;
        match self {
            NotSupported => panic!("bad unwrap"),
            Supported(t) => *t
        }
    }
}

trait HasSupport where Self: Sized {
    fn is_supported() -> IsSupported<Self>;    
}
impl HasSupport for () {
    fn is_supported() -> IsSupported<Self> { IsSupported::Supported(()) }
}
impl HasSupport for std::convert::Infallible {
    fn is_supported() -> IsSupported<Self> { IsSupported::NotSupported }
}

trait WithSupport {
    type SupportThing: HasSupport;
    
    fn has_support() -> IsSupported<Self::SupportThing> {
        <Self as WithSupport>::SupportThing::is_supported()
    }
    
    // `String` is a more complex type in my application, that cannot sensibly be constructed if the operation is not supported.
    fn get_thing(&self, h : Self::SupportThing) -> String;
}

struct X;
impl WithSupport for X {
    type SupportThing = std::convert::Infallible;
    fn get_thing(&self, h: Self::SupportThing) -> String {
        match h {}
    }
}

struct Y;
impl WithSupport for Y {
    type SupportThing = ();
    fn get_thing(&self, h: Self::SupportThing) -> String {
        "this is the thing".to_string()
    }
}

pub fn main() {
    let y = Y;
    let supported = Y::has_support();
    println!("{}", y.get_thing(supported.unwrap()));
    
    let x = X;
    let supported = X::has_support();
    assert!(matches!(supported, IsSupported::NotSupported));
}

The aforementioned trait is called WithSupport here, and IsSupported is basically Option<Token> for now. We check support through has_support, and if that returns an appropriate token, then we can retrieve the supported thing using get_thing at a later point in time. We use Infallible to avoid having to implement get_thing in cases where the operation is not supported.

I was wondering if this is an existing pattern to do this. I havenโ€™t really seen this before or wouldnโ€™t know what to call it, and I might be overcomplicating things.

5 posts - 4 participants

Read full topic

๐Ÿท๏ธ Rust_feed