Jmb
Jmb

Reputation: 23329

Deprecated alias does not generate a warning

I have a library that used to have a top-level public trait (call it FlatMap) implemented for Option. Now I have refactored my library and moved that trait into a submodule named option, and I have ensured backward compatibility by publicly importing FlatMap into the top-level of the crate. I would like to warn users of the top-level trait that it is deprecated. I have tried the following:

pub mod option {
   pub trait FlatMap<T> {}
}
#[deprecated(note="Use option::FlatMap instead")]
pub use option::FlatMap;

However this does not work: I can now use mylib::FlatMap and use mylib::option::FlatMap and either works fine with no warning. I would like the first use to generate a deprecation warning.

Note that I can't use an alias trait like this:

pub mod option {
   pub trait FlatMap<T> {}
}
#[deprecated(note="Use option::FlatMap instead")]
pub trait FlatMap<T>: option::FlatMap<T> {}
impl<T> FlatMap<T> for Option<T> {}

because I rely on implicit conversion from Option to option::FlatMap to add a method to Option instances, and the implicit conversion does not work with the aliased trait.

Upvotes: 3

Views: 357

Answers (1)

trent
trent

Reputation: 27945

Being able to call option::FlatMap methods on an Option isn't a conversion, implicit or otherwise -- it's just how methods are resolved. Trait methods may be resolved on an object if the trait has been used, but the top level FlatMap trait has no methods, so it doesn't add anything to the object at all.

It seems the best way to achieve this in Rust right now is to copy the trait (with all its contents) into the top level, annotate it #[deprecated], and write a blanket impl that defers to the "real" version.

pub mod option {
    pub trait FlatMap<T> {
        type Item;
        type FlatMap;
        fn flat_map<F>(self, f: F) -> Self::FlatMap
        where
            F: FnOnce(Self::Item) -> T;
    }
    // ...
}

#[deprecated(note = "Use option::FlatMap instead")]
pub trait FlatMap<T> {
    type Item;
    type FlatMap;
    fn flat_map<F>(self, f: F) -> Self::FlatMap
    where
        F: FnOnce(Self::Item) -> T;
}

#[allow(deprecated)]
impl<T, U> FlatMap<T> for U
where
    U: option::FlatMap<T>,
{
    type Item = U::Item;
    type FlatMap = U::FlatMap;
    fn flat_map<F>(self, f: F) -> Self::FlatMap
    where
        F: FnOnce(Self::Item) -> T,
    {
        option::FlatMap::flat_map(self, f)
    }
}

Any attempt to use the "outer" FlatMap will generate the deprecation warning (playground link).

One limitation of this approach (besides the verbosity) is that the two traits won't be compatible as trait objects (e.g., you couldn't pass an &FlatMap to a function expecting &option::FlatMap.) However, this trait isn't object safe anyway, so in this case it doesn't matter.

As it happens, your definition of flat_map -- while a generalization of and_then -- is a different generalization than several other languages use, including Rust's own Iterator::flat_map. So you might want to consider renaming it. (I think map_or_default would fit in well, name-wise, with the other methods available on Option.)

Upvotes: 2

Related Questions