Specifically, I want to do something like this:
Cargo.toml
[features]
serde = ["dep:serde", "dep:serde_with"]
[dependencies]
serde = { version = "1.0.136", optional = true }
serde_with = { version = "1.12.0", optional = true }
lib.rs
#[cfg_attr(feature = "serde", serde_with::serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Test {
#[cfg_attr(feature = "serde", serde_as(as = "serde_with::DisplayFromStr"))]
pub test: Box<dyn std::fmt::Display>,
}
However, compiling with the feature fails:
D:\repos\cad97\playground> cargo check
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
D:\repos\cad97\playground> cargo check --features serde
Checking playground v0.1.0 (D:\repos\cad97\playground)
error[E0277]: the trait bound `dyn std::fmt::Display: Serialize` is not satisfied
--> src\lib.rs:4:5
|
4 | #[cfg_attr(feature = "serde", serde_as(as = "serde_with::DisplayFromStr"))]
| ^ the trait `Serialize` is not implemented for `dyn std::fmt::Display`
|
= note: required because of the requirements on the impl of `Serialize` for `Box<dyn std::fmt::Display>`
note: required by a bound in `_serde::ser::SerializeStruct::serialize_field`
--> D:\.rust\cargo\registry\src\github.com-1ecc6299db9ec823\serde-1.0.136\src\ser\mod.rs:1899:12
|
1899 | T: Serialize;
| ^^^^^^^^^ required by this bound in `_serde::ser::SerializeStruct::serialize_field`
For more information about this error, try `rustc --explain E0277`.
If I change the helper attribute to #[serde_as(as = "serde_with::DisplayFromStr")]
, then compilation with the feature works, but compilation without the feature fails:
D:\repos\cad97\playground> cargo check --features serde
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
D:\repos\cad97\playground> cargo check
Checking playground v0.1.0 (D:\repos\cad97\playground)
error: cannot find attribute `serde_as` in this scope
--> src\lib.rs:4:7
|
4 | #[serde_as(as = "serde_with::DisplayFromStr")]
| ^^^^^^^^
error: could not compile `playground` due to previous error
Making this more frustrating is the fact that the #[serde_as]
attribute shows up in cargo expand
:
D:\repos\cad97\playground> cargo expand --features serde
Checking playground v0.1.0 (D:\repos\cad97\playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
pub struct Test {
#[serde_as(as = "serde_with::DisplayFromStr")]
pub test: Box<dyn std::fmt::Display>,
}
#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () = {
#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate serde as _serde;
#[automatically_derived]
impl _serde::Serialize for Test {
// snip
}
};
This must be some interaction between the multiple cfg_attr
gated attributes, as cfg_attr gating helper attributes seems to work with just a single gated item attribute:
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Test {
#[cfg_attr(feature = "serde", serde(with = "serde_with::rust::display_fromstr"))]
pub test: Box<dyn std::fmt::Display>,
}
(My use case actually does require the increased versatility of #[serde_as]
versus #[serde(with)]
.)
This occurs even if putting both attributes in the same cfg_attr
container (i.e. #[cfg_attr(feature = "serde", serde_with::serde_as, derive(serde::Serialize))]
).
Is there some way to make this work, or do I have to resort to just not using the #[serde_as]
helper and writing what it expands to manually?
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Test {
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub test: Box<dyn std::fmt::Display>,
}