On Wed Aug 19, 2026 at 12:23 PM BST, Danilo Krummrich wrote:
> On Mon Aug 17, 2026 at 2:56 PM CEST, Eliot Courtney wrote:
>> +    pub fn push_init<E>(&mut self, init: impl Init<T, E>, flags: Flags) -> 
>> Result<(), E>
>> +    where
>> +        E: From<AllocError>,
>
> This signature rejects impl Init<T, Infallible>, which is the reason why we 
> have
> e.g. Box::init() and Box::try_init() with different fallible signatures.
>
> So, if we follow InPlaceInit, it'd be
>
>       pub fn push_init<E>(&mut self, init: impl Init<T, E>, flags: Flags) -> 
> Result<(), Error>
>       where
>           Error: From<E>;
>
> and
>
>       pub fn try_push_init<E>(&mut self, init: impl Init<T, E>, flags: Flags) 
> -> Result<(), E>
>       where
>           E: From<AllocError>;
>
> In theory we could also simplify it to
>
>       pub fn push_init(&mut self, init: impl Init<T>, flags: Flags) -> 
> Result<(), AllocError>
>
> and
>
>       pub fn try_push_init<E>(&mut self, init: impl Init<T, E>, flags: Flags) 
> -> Result<(), E>
>       where
>           E: From<AllocError>,
>
> However, InPlaceInit actually achieves more with the init() and try_init()
> distinction:
>
>   What init() accepts, but try_init() does not accept:
>     - impl Init<T, Infallible>
>     - impl Init<T, E> where Error: From<E> but NOT E: From<AllocError>
>
>   What try_init() accepts, but init() does not accept:
>     - impl Init<T, E> where E: From<AllocError> but NOT Error: From<E>
>
> So, with the simplification we'd technically lose out on the
>
>       impl Init<T, E> where Error: From<E> but NOT E: From<AllocError>
>
> case.
>
> In any case, init() and try_init() seem a bit mixed up on their purpose
> regarding fallibility and error type strategy, but in order to really cover 
> all
> cases I think it is necessary.

I think this might also be solvable with a new trait? Something like this:

    /// Trait indicating how two distinct types should be unified.
    trait Unify<Other>: Sized {
        type Unified: From<Other> + From<Self>;
    }

    /// Types can be unified with themself.
    impl<T> Unify<T> for T {
        type Unified = T;
    }

    macro_rules! unify_rule {
        ($ty:ty; $($o:ty => $u:ty)*) => {
            impl From<Infallible> for $ty {
                fn from(v: Infallible) -> Self {
                    match v {}
                }
            }
            
            impl Unify<Infallible> for $ty {
                type Unified = $ty;
            }
            
            impl Unify<$ty> for Infallible {
                type Unified = $ty;
            }
            
            $(impl Unify<$o> for $ty {
                type Unified = $u;
            }
            
            impl Unify<$ty> for $o {
                type Unified = $u;
            })*
        }
    }

    macro_rules! unify {
        ($a: ty, $b: ty) => {
            <$a as Unify<$b>>::Unified
        }
    }

    unify_rule!(Error; );
    unify_rule!(AllocError; Error => Error);

and you just need to write

    pub fn push_init<E>(&mut self, init: impl Init<T, E>, flags: Flags) -> 
Result<(), unify!(E, AllocError)>

This looks like a lot of work to impl, but realistically this is just an
additional trait impl near where you'd the put the `From` impl.

We can even provide an attribute macro `#[unify]` so you just need to stick it
on your `From` impl, e.g.

    #[unify]
    impl From<AllocError> for Error {
        ...
    }

would generate

    impl Unify<AllocError> for Error { ... }
    impl Unify<Error> for AllocError { ... }

Best,
Gary

Reply via email to