Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SuperFrom for more optional prop types including custom types #3476

Open
Tired-Fox opened this issue Dec 31, 2024 · 3 comments
Open

SuperFrom for more optional prop types including custom types #3476

Tired-Fox opened this issue Dec 31, 2024 · 3 comments
Labels
core relating to the core implementation of the virtualdom enhancement New feature or request

Comments

@Tired-Fox
Copy link

Tired-Fox commented Dec 31, 2024

Feature Request

So right now, as of v0.6.1, optional component props are only supported for specific types when also using #[props(into)]. It would be helpful if it was also usable with Option<MyCustomType> given the custom type meets the prop requirements.

I currently wrote a custom Option that implements SuperFrom for any inner type that implements Into for the target type. This would mean something like this would work.

use dioxus::prelude::*;

#[component]
fn Custom(
   #[props(into)]
   state: Option<MyCustomType>
) -> Element {
    VNode::empty()
}

Currently, there is an error since SuperFrom is not implemented for Option<MyCustomType>

Implement Suggestion

Something like the snippet below may work. This would also mean that it would be implemented for anything that implements From or Into between the types. If this isn't desired, then maybe a new Trait that can be derived to convert between the types and would automatically allow it to be used as a prop in Option<MyCustomType>

pub struct OptionFromValue;
impl<T: Into<U>, U> SuperFrom<T, OptionalFromValue> for Option<U> {
    fn super_from(value: T) -> Self {
        Some(Into::<U>::into(value)) 
    }
}
@ealmloff
Copy link
Member

ealmloff commented Jan 2, 2025

You can do this today with a custom SuperFrom impl for a specific type. It just doesn't work with the From trait by default. This code doesn't compile:

use dioxus::prelude::*;

fn main() {
    launch(|| rsx! { Custom {
        state: "hello world"
    } }); 
}

#[component]
fn Custom(
   #[props(into)]
   state: Option<MyCustomType>
) -> Element {
    VNode::empty()
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct MyCustomType;

impl From<&str> for MyCustomType {
    fn from(_: &str) -> Self {
        Self
    }
}

But this version does:

use dioxus::prelude::*;

fn main() {
    launch(|| rsx! { Custom {
        state: "hello world"
    } }); 
}

#[component]
fn Custom(
   #[props(into)]
   state: Option<MyCustomType>
) -> Element {
    VNode::empty()
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct MyCustomType;

impl SuperFrom<&str, MyCustomType> for Option<MyCustomType> {
    fn super_from(_: &str) -> Option<MyCustomType> {
        Some(MyCustomType)
    }
}

Something like the snippet below may work. This would also mean that it would be implemented for anything that implements From or Into between the types. If this isn't desired, then maybe a new Trait that can be derived to convert between the types and would automatically allow it to be used as a prop in Option

I don't think that implementation is possible for the std version of Option because it already implements From<T> for Option<T>. Marker specialization lets us "assert" that two impls don't overlap, but it doesn't allow us to use overlapping trait implementations. In this case, From<T> for Option<T> and From<impl Into<T>> for Option<T> overlap in practice so that assertion doesn't help.

The implementation for specific types (like Option<MyCustomType> above and Option<String> in dioxus-core) don't overlap with the default From<T> for Option<T>. Proper specialization in rust would let us set some sort of priority for each implementation to help rust choose which one to apply by default. Deref specialization might let us do something similar with stable rust but the implementation is a lot messier

@ealmloff ealmloff added enhancement New feature or request core relating to the core implementation of the virtualdom labels Jan 2, 2025
@Tired-Fox
Copy link
Author

I just want to clarify, because I still think it would work without overlaps. Here is an example that I came up with that is similar to SuperFrom and SuperInto. There are probably parts of the codebase that I am not accounting for, but would something similar to this work?

trait MyInto<O, M = ()> {
    fn my_into(self) -> O;
}

impl<T, O, M> MyInto<O, M> for T
where 
    O: MyFrom<T, M>
{
    fn my_into(self) -> O {
        O::my_from(self)
    }
}

trait MyFrom<T, M = ()> {
    fn my_from(_: T) -> Self;
}

struct ValueToOptionalMyFrom;
impl<T, U> MyFrom<T, ValueToOptionalMyFrom> for Option<U>
where
    T: Into<U>
{
    fn my_from(value: T) -> Self {
        Some(Into::<U>::into(value))
    }
}

struct OptionalToOptionalMyFrom;
impl<T, U> MyFrom<Option<T>, OptionalToOptionalMyFrom> for Option<U>
where
    T: Into<U>
{
    fn my_from(value: Option<T>) -> Self {
        value.map(Into::into)
    }
}

impl<T, O> MyFrom<T, ()> for O
where
    O: From<T>
{
    fn my_from(value: T) -> Self {
        Self::from(value)
    }
}

fn test<P, M>(value: P)
where
    P: MyInto<Option<String>, M>
{
    let value = value.my_into();
    println!("{value:?}")
}

@ealmloff
Copy link
Member

ealmloff commented Jan 3, 2025

I just want to clarify, because I still think it would work without overlaps. Here is an example that I came up with that is similar to SuperFrom and SuperInto. There are probably parts of the codebase that I am not accounting for, but would something similar to this work?

trait MyInto<O, M = ()> {
    fn my_into(self) -> O;
}

impl<T, O, M> MyInto<O, M> for T
where 
    O: MyFrom<T, M>
{
    fn my_into(self) -> O {
        O::my_from(self)
    }
}

trait MyFrom<T, M = ()> {
    fn my_from(_: T) -> Self;
}

struct ValueToOptionalMyFrom;
impl<T, U> MyFrom<T, ValueToOptionalMyFrom> for Option<U>
where
    T: Into<U>
{
    fn my_from(value: T) -> Self {
        Some(Into::<U>::into(value))
    }
}

struct OptionalToOptionalMyFrom;
impl<T, U> MyFrom<Option<T>, OptionalToOptionalMyFrom> for Option<U>
where
    T: Into<U>
{
    fn my_from(value: Option<T>) -> Self {
        value.map(Into::into)
    }
}

impl<T, O> MyFrom<T, ()> for O
where
    O: From<T>
{
    fn my_from(value: T) -> Self {
        Self::from(value)
    }
}

That implementation fails to infer in this case:

fn test() {
    let value: Option<i32> = 0i32.my_into();
    println!("{value:?}")
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core relating to the core implementation of the virtualdom enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants