From 2e3c387ae126e4c6333f7021febf8351729b429f Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jan 2024 23:50:23 -0500 Subject: [PATCH] docs: better info about writing types containing data --- crates/keyfork-shard/src/lib.rs | 8 +- crates/keyfork-shard/src/openpgp.rs | 8 +- crates/keyfork/src/cli/recover.rs | 2 +- crates/util/keyfork-mnemonic-util/src/lib.rs | 12 +- docs/src/SUMMARY.md | 1 + docs/src/dev-guide/handling-data.md | 118 +++++++++++++++++++ 6 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 docs/src/dev-guide/handling-data.md diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index 91f99cf..1ea977d 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -78,8 +78,8 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box Result<(), Box`]. + pub fn to_bytes(&self) -> Vec { + self.entropy.to_vec() + } + /// Drop self, returning the decoded data. - pub fn to_bytes(self) -> Vec { + pub fn into_bytes(self) -> Vec { self.entropy } /// Clone the existing entropy. + #[deprecated] pub fn entropy(&self) -> Vec { self.entropy.clone() } /// Create a BIP-0032 seed from the provided data and an optional passphrase. + /// + /// # Errors + /// The method may return an error if the pbkdf2 function returns an invalid length, but this + /// case should not be reached. pub fn seed<'a>( &self, passphrase: impl Into>, diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index b5b38c2..057d691 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -25,6 +25,7 @@ # Developers Guide +- [Handling Data](./dev-guide/handling-data.md) - [Writing Binaries](./dev-guide/index.md) - [Provisioners](./dev-guide/provisioners.md) - [Auditing Dependencies](./dev-guide/auditing.md) diff --git a/docs/src/dev-guide/handling-data.md b/docs/src/dev-guide/handling-data.md new file mode 100644 index 0000000..9ad4b4c --- /dev/null +++ b/docs/src/dev-guide/handling-data.md @@ -0,0 +1,118 @@ +# Handling Data + +In Rust, it is common to name things `as_*`, `to_*`, and `into_*`. These three +methods perform three different, but related, functions, and it's important to +choose the correct name when writing methods. These methods take data that are +stored inside of the struct, and return them in some manner. For example, let's +look at the `Mnemonic` struct: + +```rust +pub struct Mnemonic { + entropy: Vec, + // fields omitted +} +``` + +We're going to define the three methods `as_bytes()`, `to_bytes()`, and +`into_bytes()`, explaining what the details of each are. To start with: + +### `Mnemonic::as_bytes()` + +This method will return a `&[u8]`, a reference to a slice of bytes, without +allocating anything. The `Mnemonic` type is not consumed by this process. + +```rust +impl Mnemonic { + /// Return a slice of bytes represented by the mnemonic. + pub fn as_bytes(&self) -> &[u8] { + &self.entropy + } +} +``` + +### `Mnemonic::to_bytes()` + +This method will return a `Vec`, an allocated type containing the bytes, +without consuming the `Mnemonic` type. This is useful if you may need the +`Mnemonic` type in the future. + +```rust +impl Mnemonic { + /// Return a `Vec` of bytes represented by the mnemonic. + pub fn to_bytes(&self) -> Vec { + self.entropy.to_vec() + } +} +``` + +### `Mnemonic::into_bytes()` + +This method will return the internal representation of bytes. No allocation is +performed, but the `Mnemonic` value is no longer accessible. + +```rust +impl Mnemonic { + /// Return a Vec of bytes represented by the mnemonic. + pub fn into_bytes(self) -> Vec { + self.entropy + } +} +``` + +## Creating Data: `Mnemonic::seed()` + +Sometimes you may have an operation (such as creating a seed from a mnemonic) +which can't possibly happen without allocating some kind of data. In that case, +it is not reasonable - or even possible - to have three methods for that case. +In that case, it is reasonable to have a method with a "bare" name, such as +`Mnemonic::seed()`. Additionally, unless it makes sense to make the type +unusable after such an operation (using the type system to your advantage), it +makes sense to borrow from self while returning the allocated value. + +```rust +impl Mnemonic { + /// Create a seed from the mnemonic's data. + pub fn seed(&self) -> Result, Error> { + let result = perform_secret_operation(self.entropy)?; + Ok(result) + } +} +``` + +## Complex Example: `Mnemonic::into_parts()` + +Let's add a `Wordlist` to our `Mnemonic` type, so we can format it as a string. + +```rust +pub struct Mnemonic { + entropy: Vec, + wordlist: Wordlist, +} +``` + +Because we now have two expensive-to-allocate variables in our type, we need to +change how we deconstruct it into parts: + +```rust +impl Mnemonic { + /// Split the Mnemonic into its interior parts. + pub fn into_parts(self) -> (Vec, Wordlist) { + let Mnemonic { entropy, wordlist } = self; + (entropy, wordlist) + } + + /// Return a Vec of bytes represented by the mnemonic. + pub fn into_bytes(self) -> Vec { + self.into_parts().0 + } + + /// Return the Wordlist used by the mnemonic. + pub fn into_wordlist(self) -> Wordlist { + self.into_parts().1 + } +} +``` + +In practical use, `Mnemonic` stores `wordlist` as an `Arc` to allow reusing +the same wordlist (parsing it is expensive!), but we'll ignore that for +demonstration purposes.