3 releases
0.1.2 | May 15, 2019 |
---|---|
0.1.1 | May 3, 2019 |
0.1.0 | Apr 30, 2019 |
#2083 in Rust patterns
24KB
348 lines
len_constraints
Welcome to len_constraints
🎉
About
This crate implements traits and types that allows you to implement type-pinned length constraints in your API.
Why?
How often have you seen APIs like this?
// BAD EXAMPLE!
fn encrypt(buf: &mut[u8], plaintext: &[u8], key: &[u8], nonce: &[u8])
-> Result<usize, Box<dyn Error + 'static>>
{
// Validate parameters
if plaintext.len() > 65_635 { Err("Plaintext is too large")? }
if buf.len() < plaintext.len() + 16 { Err("Buffer is smaller than plaintext length")? }
if key.len() != 32 { Err("Invalid key size")? }
if nonce.len() != 12 { Err("Invalid nonce size")? }
// Do sth.
unimplemented!()
}
As you can see, this API is pretty opaque and requires a lot of manual checks.
Of course s.o. could use array references:
// MEH EXAMPLE...
fn encrypt(buf: &mut[u8], plaintext: &[u8], key: &[u8; 32], nonce: &[u8; 12])
-> Result<usize, Box<dyn Error + 'static>>
{
// Validate parameters
if plaintext.len() > 65_635 { Err("Plaintext is too large")? }
if buf.len() < plaintext.len() + 16 { Err("Buffer is smaller than plaintext length")? }
// Do sth.
unimplemented!()
}
But array references also have their disadvantages. They are not suitable for multiple valid lengths
(allow anything in 16..=32
) nor can they represent relative relationships. Also converting between
other data types and arrays can get annoying.
len_constraints
tries to solve this problem:
// GOOD EXAMPLE :D
use std::{ convert::TryInto, error::Error };
use len_constraints::{
slice_mut::RelativeMut, slice::{ Fixed, Ranged },
type_math::{ Add, _0, _12, _16, _32, _65536 }
};
fn encrypt(buf: RelativeMut<u8, Add, _16>, plaintext: Ranged<u8, _0, _65536>,
key: Fixed<u8, _32>, nonce: Fixed<u8, _12>) -> Result<usize, Box<Error + 'static>>
{
// Get buffer (we do this here because there may not be a relationship at an earlier stage)
let buf = buf.get_slice_mut(plaintext.len())?;
// Do sth.
Ok(7)
}
fn main() -> Result<(), Box<Error + 'static>> {
// Parameters
let mut buf: &mut[u8] = &mut[0; 9 + 16];
let plaintext: &[u8] = b"Testolope";
let key: &[u8] = &[0; 32];
let nonce = "12 byte Nonc".as_bytes();
// Call function
encrypt(buf.into(), plaintext.try_into()?, key.try_into()?, nonce.try_into()?)?;
Ok(())
}
As you can see, we now can describe complex relationships in the function signature – this makes the API more transparent and removes the need for manual (and error-prone) parameter validation. Also, the API is slice-friendly.