So let's say you have a struct defined like this:
pub struct Password(String) that implements AsRef, and you are writing a chat bot. The function to send a message has the signature:
pub fn send<Msg: AsRef<str>>(&mut client, msg: Msg) -> Result<Message, Error>. It would be a bit concerning if you could just pass in password, as that would be a horrific bug if it doesn't get caught. So i propose a solution: the
Sensitive trait. It is an auto trait that gets implemented if any of the values inside a struct implement
Sensitive. Then, the signature of the message sending function could be this:
pub fn send<Msg: AsRef<str> + !Sensitive>(&mut client, msg: Msg) -> Result<Message, Error>. The problem is, using negated traits is unstable (Correct me if I'm wrong).
Another idea is the reverse, this could be more feasible: a
NonSensitive auto trait, removing the need for negated traits.
A few handy things here could be a
SensitiveValue<T>(T) struct, which is a wrapper that implements both
Deref traits and
Sensitive (or !UnSensitive), and a
NonSensitive<T>(T) struct, which does the opposite.
If you need a more real-world example, think about what could happen if someone lazy names their variables with single letters (
x) (Definitely not me), and accidentally makes a typo that sends an AWS token instead of the MOTD.
Negative trait bounds will likely never be stabilized like this because they make implementing a trait a major change, which is not something Rust wants. This seems like a complicated solution to the problem, because now every library that's "sending away" things needs these bounds.
Another problem with this is that the definition of "sensitive data" varies wildly. For example, you do want to send a password over the network to a login endpoint. In the big picture of Rust programs, there is no simple binary of "secret, must not be shared at all" and "public, can be shared everywhere".
But in your example, the solution is fairly simple: Don't implement
AsRef<str> for your password type. This way, it cannot be expose accidentally. Rusts type system allows this nicely, you can even zeroize it in
Drop. Use Rusts type system to your advantave and we have no reason for such a trait.
The solution in the Rust fashion is a lot simpler: your new
struct Password comes without methods or traits by default, i.e. you express all operations explicitly. Just remove the
impl AsRef<str> and add an aptly named method instead, then you can easily audit your codebase for places that reveal the string.
Even better, use a dedicated crate for this purpose, like secstr.
This crate also prevents accidental printing or logging of secrets, swapping of the memory block that holds the secret out to disk; and securely zeroes out the memory when you drop it.