SuperFrom for more optional prop types including custom types
Opened this issue · 3 comments
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))
}
}
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
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:?}")
}
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
andSuperInto
. 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:?}")
}