-
-
Save darconeous/adca2a6b84374c15c071d96cc986c888 to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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 str, PhantomData<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