Define 'override' for trait without negative traits

  • The trait IntoView transforms an arbitrary value into an object which implements View.
  • The trait IntoCloseView transforms an arbitrary object via individually-specified behaviours into an object which implements IntoView. It is used because the blanket-implementation may not be applicable to that particular structure.

However, I'd like the IntoCloseView trait to be implemented for all values implementing IntoView because it allows any object to be used in place of the objects implementing IntoCloseView.

Code

trait IntoView {
  fn into_view(self) -> impl View;
}

trait IntoCloseView {
  fn into_raw_view(self) -> impl IntoView;
}

impl<Str> IntoView for Str where Str: AsRef<str> {
  fn into_view(self) ...
}

impl<V> IntoCloseView for V where V: IntoView {
  fn into_raw_view(self) -> impl IntoView {
   self.into_view()
  }
}

impl<Str> IntoCloseView for Str where Str: AsRef<str> {
  fn into_raw_view(self) -> impl IntoView {
    // Custom function
  }
}

The issue arrises at the final impl block. The IntoCloseView implementations conflict because dyn AsRef<str>: IntoView

With negative trait bounds, I'd be able to do the following:

impl<V> IntoCloseView for V where V: IntoView + !AsRef<str> {
  ...
} 

But they don't exist yet. I'm hoping for advice.

Context

The backround to my question is in GUI. I'm using the Leptos framework to build a web app. The IntoView and View traits come directly from there and they're implemented for almost everything.

Currently, I'm using the IntoCloseTrait exclusively to override the titlebar of modal prompts, but it may be extended into other areas.

Currently, the modal component looks like this:

pub fn Prompt<Heading>(heading: Heading) -> impl IntoView where Heading: IntoCloseView {
  ...
}

impl<Str> IntoCloseView for Str where Str: AsRef<str> {
  fn into_close_view(self) -> impl IntoView {
    view!(<Button>{self}</Button>)
  }
} 

// Because `AsRef<str>` implements `IntoCloseView`, the result of `into_close_view(self)` is not just the `String`, but in this case a button.
view!(<Prompt heading="Just some title" />);

// However, since f64 doesn't have a special implementation for `IntoCloseView`, the result of `IntoCloseView` is simply the result of `IntoView`
view!(<Prompt heading=12.5f64 />)

Thanks

This is called specialization, and it's not possible in Rust. (It never may be — even the current minimal impl on nightly is incomplete and unsound.)

Ah shame. have you got any suggestions, potentially involving architectural changes?

Yes, only architectural changes. But we'd probably need to know about the domain-specific semantics in orser useful and more concrete help. What are your "view" objects? Are you building a GUI?

Yep. I'm building a GUI with the Leptos framework. I'll update my question with additional context.

See update

I think a simpler solution in this case would be to create different newtypes that implement IntoCloseView by forwarding to an IntoView (or an entirely custom impl), and drop all blanket impls whatsoever (I'd suggest you drop the AsRef<str> blanket impl as well). This way, you will always explicitly disambiguate between use cases, and there won't be any blanket impls unexpectedly colliding with more concrete impls.

Generally, I don't recommend adding blanket impls unless you know that it only ever makes sense to write such an impl in exactly one way – which is clearly not the case here. Adding a blanket impl for a special case will cause a lot of grief – it essentially makes one special case work at the expense of literally everything else, which is bad developer UX.

struct CloseViewWrapper<T>(T);

impl<T: IntoView> IntoCloseView for CloseViewWrapper<T> {
    fn into_close_view(self) -> impl IntoView {
        self.0.into_view()
    }
}

struct StrLikeCloseView<S>(S);

impl<S: AsRef<str>> IntoCloseView for StrLikeCloseView<S> {
    fn into_close_view(self) -> impl IntoView {
        view!(<Button>{self.0}</Button>)
    }
}

Amazingggg thank yuu

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.