keyfork/docs/src/dev-guide/handling-data.md

121 lines
3.4 KiB
Markdown

{{#include ../links.md}}
# 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<u8>,
// 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<u8>`, 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<u8>` of bytes represented by the mnemonic.
pub fn to_bytes(&self) -> Vec<u8> {
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<u8> of bytes represented by the mnemonic.
pub fn into_bytes(self) -> Vec<u8> {
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<Vec<u8>, 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<u8>,
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<u8>, Wordlist) {
let Mnemonic { entropy, wordlist } = self;
(entropy, wordlist)
}
/// Return a Vec<u8> of bytes represented by the mnemonic.
pub fn into_bytes(self) -> Vec<u8> {
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<T>` to allow reusing
the same wordlist (parsing it is expensive!), but we'll ignore that for
demonstration purposes.