Skip to content

Instantly share code, notes, and snippets.

@rust-play
Created May 29, 2019 01:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rust-play/88f862a4fc4bcd013a4d90c533a5e9eb to your computer and use it in GitHub Desktop.
Save rust-play/88f862a4fc4bcd013a4d90c533a5e9eb to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
/// Demonstration of compile-time key-value type safety.
///
/// This program demonstrates how you use Rust generics to encode type
/// information into the keys for a key-value store. This allows for
/// type-safe access to data in the store, preventing things like writing
/// strings to keys that should only contain integers. Attempting to
/// do so results in a compile-time error.
///
/// The example code is at the top of this file, the implementation details
/// are below the example.
///
use std::marker::PhantomData;
pub const AUTH: TypedKey<&[u8]> = TypedKey("auth", PhantomData);
pub const HOST: TypedKey<&str> = TypedKey("host", PhantomData);
pub const PORT: TypedKey<u16> = TypedKey("port", PhantomData);
fn main() {
let mut store = KeyValueStore;
// Setter example.
store.set_key_value(HOST, "example.com");
store.set_key_value(PORT, 1234);
store.set_key_value(AUTH, b"password");
// Getter example.
let value: &str = store.get_key_value(HOST).unwrap();
println!("host -> {:?}", value);
let value: u16 = store.get_key_value(PORT).unwrap();
println!("port -> {:?}", value);
let value: &[u8] = store.get_key_value(AUTH).unwrap();
println!("auth -> {:?}", value);
// The lines below this point do not compile,
// because the type does not match the key.
//store.set_key_value(HOST, b"example.com");
//store.set_key_value(PORT, 91234);
//store.set_key_value(AUTH, "password");
//let value: u16 = store.get_key_value(HOST).unwrap();
//let value: () = store.get_key_value(PORT).unwrap();
//let value: &str = store.get_key_value(AUTH).unwrap();
}
///////////////////////////////////////////////////////////////////////////////
// IMPLEMENTATION DETAILS /////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
use std::convert::{From, Into, TryFrom, TryInto};
/// Key class for our key-value store that also encodes value type information.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypedKey<T: 'static + ?Sized>(&'static str, PhantomData<&'static T>);
/// The underlying value format of our key-value store. This could also be a
/// JSON datastore, or an opaque binary blob of some sort.
#[derive(Debug)]
pub enum CheckedValue<'a> {
Empty,
String(&'a str),
Integer(u32),
Bytes(&'a [u8]),
}
// Every type that is used with a `TypeKey` that will be "set" needs to have an
// implementation of `std::convert::From` to convert the native value into the
// underlying value format:
impl<'a> From<u16> for CheckedValue<'a> {
fn from(value: u16) -> Self {
CheckedValue::Integer(value as u32)
}
}
impl<'a> From<&'a str> for CheckedValue<'a> {
fn from(value: &'a str) -> Self {
CheckedValue::String(value)
}
}
impl<'a> From<&'a [u8]> for CheckedValue<'a> {
fn from(value: &'a [u8]) -> Self {
CheckedValue::Bytes(value)
}
}
impl<'a> From<()> for CheckedValue<'a> {
fn from(_: ()) -> Self {
CheckedValue::Empty
}
}
// Every type that is used with a `TypeKey` that will be retrieved needs to
// have an implementation of `std::convert::TryFrom` to convert from the
// underlying format to the native value:
impl<'a> TryFrom<CheckedValue<'a>> for u16 {
type Error = ();
fn try_from(value: CheckedValue<'a>) -> Result<Self, Self::Error> {
match value {
CheckedValue::Integer(x) => Ok(x as u16),
_ => Err(()),
}
}
}
impl<'a> TryFrom<CheckedValue<'a>> for &'a str {
type Error = ();
fn try_from(value: CheckedValue<'a>) -> Result<Self, Self::Error> {
match value {
CheckedValue::String(x) => Ok(x),
_ => Err(()),
}
}
}
impl<'a> TryFrom<CheckedValue<'a>> for &'a [u8] {
type Error = ();
fn try_from(value: CheckedValue<'a>) -> Result<Self, Self::Error> {
match value {
CheckedValue::Bytes(x) => Ok(x),
_ => Err(()),
}
}
}
impl<'a> TryFrom<CheckedValue<'a>> for () {
type Error = ();
fn try_from(value: CheckedValue<'a>) -> Result<Self, Self::Error> {
match value {
CheckedValue::Empty => Ok(()),
_ => Err(()),
}
}
}
/// Our very dumb (but type-safe) key/value store.
struct KeyValueStore;
impl KeyValueStore {
fn set_key_value<'a, T>(&mut self, key: TypedKey<T>, value: T)
where
T: Into<CheckedValue<'a>>,
{
match value.into() {
CheckedValue::Empty => println!("{} <- (empty)", key.0),
CheckedValue::Integer(x) => println!("{} <- {:?}", key.0, x),
CheckedValue::String(x) => println!("{} <- {:?}", key.0, x),
CheckedValue::Bytes(x) => println!("{} <- {:?}", key.0, x),
}
}
fn get_key_value_raw(&mut self, key: &str) -> Result<CheckedValue<'static>, ()> {
match key {
"host" => Ok(CheckedValue::String("example.com")),
"port" => Ok(CheckedValue::Integer(1234)),
"auth" => Ok(CheckedValue::Bytes(b"password")),
_ => Err(()),
}
}
fn get_key_value<T>(&mut self, key: TypedKey<T>) -> Result<T, ()>
where
T: TryFrom<CheckedValue<'static>, Error = ()>,
{
self.get_key_value_raw(key.0)?.try_into()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment