I currently have code that looks somewhat like this:
pub trait Bla {
#[clf(feature = "serde")]
type Ta: A + B + C + D + E + F + G + Serialize
#[clf(not(feature = "serde"))]
type Ta: A + B + C + D + E + F + G
#[clf(feature = "serde")]
type Ta_two: H + I + J + K + L + M + Serialize
#[clf(not(feature = "serde"))]
type Ta_two: H + I + J + K + L + M
fn func(x: Ta) -> usize;
...
}
That seems quite SemVer/crate composition unfriendly in general. Can you say more about the intention / motivation? Perhaps a conditional subtrait with a blanket implementation would be suitable.
(Click for an example of why it can be unfriendly.)
That's a non-additive feature:
I add your crate
I implement Bla but my Ta is not Serialize
I add another crate that has you as a dep with the serde feature[1]
Compilation now fails
...and a no_serde feature would be too:
I add your crate
I implement some fn<T: Bla>(arg: Bla::Ta) and get to use Serialize in the body
I add another crate that has you as a dep with the no_serde feature[2]
Compilation now fails
or someone with such a dep tries to add my crate ↩︎
or someone with such a dep tries to add my crate ↩︎
Are you writing a library crate that you might publish for others to use? If you are, you should not change trait bounds like this because it creates a library that is difficult to use reliably. That's what @quinedot is saying.
Either way, us more about what you need this for, and maybe a better way could be found.
There isn’t a stable syntax for conditionally compiled trait bounds. On nightly, this is allowed:
#![feature(where_clause_attrs)]
pub trait Bla {
type Ta: A + B + C + D + E + F + G
where
#[cfg(feature = "serde")]
Self::Ta: Serialize;
}
Are you writing a library crate that you might publish for others to use?
I suspect that that is the aim of my colleagues, yes.
Tell us more about what you need this for, and maybe a better way could be found.
I will ask my colleagues to be sure, but I was guessing one might want to compile without any serialization support, e.g., for size or dependency reasons (e.g., embedded or minimal environments)? Or for benchmarking raw efficiency / performance?
Regardless, isn't features = ["serde"] quite a common sight in a Cargo.toml?
Yes, but such a feature will generally add impl Serialize for ..., which doesn't break code not using serialization, not add + Serialize trait bounds, which will break downstream code that implements your trait without also implementing Serialize.
In this case, the Bla trait should not have anything to say about serialization; rather, the Serialize impl can and should have a T::Ta: Serialize bound.
This way, Bar can still be used with types which do not implement Serialize, if the usage does not involve serialization, regardless of the state of feature = "serde".
Thanks, that seems to work. Now what do I do with impl {...} blocks? They don't seem to recognise that T::Ta has the trait bound Serialize or Deserialize. Add the traits in the where part?
What impl blocks of yours have this problem? Why do they care about serialization?
They don't seem to recognise that T::Ta has the trait bound Serialize or Deserialize.
Yes, all such bounds are to be specified perimpl block. The point of this is that all functionality unrelated to serialization keeps working when T::Tadoesn't implement Serialize.
Add the traits in the where part?
Yes, that is how you would do it when it is necessary. Such impl blocks must also have #[cfg(feature = "serde")].