A function that takes in only arguments that are not `Cloneable`?

How do I write a function that takes in only arguments that are not Cloneable or Copyable?

e.g.

Something like:

fn func_with_non_copyable_param(non_copyable_param: impl !Cloneable) {
    ... // implementations
}

What's the point of such a restriction?

Rust doesn't currently support any kind of negative reasoning about traits — it's hard to get right, and also, it would mean that adding a Clone implementation in a library could be a breaking change. So, if it ever happens, it'll only apply to cases where the type has an explicit impl !Clone for Foo {} promising that the lack of Clone is intentional rather than accidental.

However, you don't necessarily need that. If you write code generic over some type T and don't also write a T: Clone or T: Copy bound, then your code cannot in any way depend on the type in fact being cloneable.

You could also require that the value be put into a wrapper type that you define which doesn't implement Clone and doesn't hand out references to the contents.

If that doesn't help you, then tell us more about your requirements — what do you want to achieve by having this restriction?

9 Likes

Hi @kpreid and @steffahn,

Thank you both for your responses.

Context

Right now, I am attempting to enhance my crate sosecrets-rs, I am exploring if not allowing T: Copy to be wrapped by Secret<T, MEC: typenum::Unsigned, EC, typenum::Unsigned> would be a good idea.

Secret<T, MEC: typenum::Unsigned, EC, typenum::Unsigned> uses the typenum crate to enable compile time counting of the number of exposure of the secret and if EC is greater than MEC, the program would not compile.

Here is an example of how one can use it:

use sosecrets_rs::{
  prelude::*,
  traits::ExposeSecret,
};
use typenum::U2;

// Define a secret with a maximum exposure count of 2
let secret = Secret::<_, U2>::new("my_secret_value".to_string());

// Expose the secret and perform some operations with the exposed value; secret has been exposed once: `EC` = 1, `MEC` = 2;
let (next_secret, exposed_value) = secret.expose_secret(|exposed_secret| {
    // `exposed_secret` is only 'available' from the next line -------
    assert_eq!(&*exposed_secret.as_str(), "my_secret_value"); //     ^
    // Perform operations with the exposed value                     |
    // ...                                                           v
    // to this line... -----------------------------------------------
});

Disallowing T: Copy to be wrapped by Secret will enhance it by completely disallowing secret from 'escaping' the scope of the closure. But again, it will also greatly limit its use as Bytes etc are all Copy.

If I understand your API correctly, disallowing Copy and Clone won't prevent a user from smuggling out the secret. You'd also need to prevent ToOwned, ToString, Debug, Display, etc.

5 Likes

I think you have understood my API correctly.

And don't forget that you'd also need to prevent the user from implementing any traits on the secret type that expose the secret, and from having a secret type that exposes itself directly via a method.

Given the goal of the API, it's almost certainly going to be easier to have a trait SecretContainer, and require users to implement that for anything they want kept secret (using documentation to remind people that a SecretContainer should not implement Clone, Copy, AsRef, ToString or other leak routes), than to ban all possible traits and implementations that might be used to leak the secret.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.