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

3.4 KiB

{{#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:

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.

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.

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.

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.

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.

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:

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.