Skip to content

Commit

Permalink
Rework structured value casting (#396)
Browse files Browse the repository at this point in the history
Also adds a const-fn based mechanism for pulling concrete values out of generic ones
  • Loading branch information
KodrAus authored Aug 3, 2020
1 parent ca54ac7 commit 803a23b
Show file tree
Hide file tree
Showing 10 changed files with 667 additions and 190 deletions.
30 changes: 30 additions & 0 deletions benches/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![cfg(feature = "kv_unstable")]
#![feature(test)]

extern crate log;
extern crate test;

use log::kv::Value;

#[bench]
fn u8_to_value(b: &mut test::Bencher) {
b.iter(|| Value::from(1u8))
}

#[bench]
fn u8_to_value_debug(b: &mut test::Bencher) {
b.iter(|| Value::from_debug(&1u8))
}

#[bench]
fn str_to_value_debug(b: &mut test::Bencher) {
b.iter(|| Value::from_debug(&"a string"))
}

#[bench]
fn custom_to_value_debug(b: &mut test::Bencher) {
#[derive(Debug)]
struct A;

b.iter(|| Value::from_debug(&A))
}
61 changes: 60 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,72 @@
//! atomics and sets `cfg` flags accordingly.
use std::env;
use std::process::Command;
use std::str::{self, FromStr};

#[cfg(feature = "kv_unstable")]
#[path = "src/kv/value/internal/cast/primitive.rs"]
mod primitive;

fn main() {
let target = env::var("TARGET").unwrap();
let minor = match rustc_minor_version() {
Some(minor) => minor,
None => return,
};

let target = match rustc_target() {
Some(target) => target,
None => return,
};

// If the target isn't thumbv6 then we can use atomic CAS
if !target.starts_with("thumbv6") {
println!("cargo:rustc-cfg=atomic_cas");
}

// If the Rust version is at least 1.46.0 then we can use type ids at compile time
if minor >= 47 {
println!("cargo:rustc-cfg=const_type_id");
}

// Generate sorted type id lookup
#[cfg(feature = "kv_unstable")]
primitive::generate();

println!("cargo:rustc-cfg=srcbuild");
println!("cargo:rerun-if-changed=build.rs");
}

fn rustc_target() -> Option<String> {
env::var("TARGET").ok()
}

// From the `serde` build script
fn rustc_minor_version() -> Option<u32> {
let rustc = match env::var_os("RUSTC") {
Some(rustc) => rustc,
None => return None,
};

let output = match Command::new(rustc).arg("--version").output() {
Ok(output) => output,
Err(_) => return None,
};

let version = match str::from_utf8(&output.stdout) {
Ok(version) => version,
Err(_) => return None,
};

let mut pieces = version.split('.');
if pieces.next() != Some("rustc 1") {
return None;
}

let next = match pieces.next() {
Some(next) => next,
None => return None,
};

u32::from_str(next).ok()
}
6 changes: 3 additions & 3 deletions src/kv/value/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
use std::fmt;

use super::internal::{Erased, Inner, Visitor};
use super::internal::{Inner, Visitor};
use super::{Error, Value};

impl<'v> Value<'v> {
/// Get a value from a fillable slot.
pub fn from_fill<T>(value: &'v T) -> Self
where
T: Fill + 'static,
T: Fill,
{
Value {
inner: Inner::Fill(unsafe { Erased::new_unchecked::<T>(value) }),
inner: Inner::Fill(value),
}
}
}
Expand Down
56 changes: 19 additions & 37 deletions src/kv/value/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,6 @@ use std::fmt;

use super::{Primitive, ToValue, Value};

macro_rules! impl_into_owned {
($($into_ty:ty => $convert:ident,)*) => {
$(
impl ToValue for $into_ty {
fn to_value(&self) -> Value {
Value::from(*self)
}
}

impl<'v> From<$into_ty> for Value<'v> {
fn from(value: $into_ty) -> Self {
Value::from_primitive(value as $convert)
}
}
)*
};
}

impl<'v> ToValue for &'v str {
fn to_value(&self) -> Value {
Value::from(*self)
Expand Down Expand Up @@ -67,25 +49,25 @@ where
}
}

impl_into_owned! [
usize => u64,
u8 => u64,
u16 => u64,
u32 => u64,
u64 => u64,

isize => i64,
i8 => i64,
i16 => i64,
i32 => i64,
i64 => i64,

f32 => f64,
f64 => f64,

char => char,
bool => bool,
];
macro_rules! impl_to_value_primitive {
($($into_ty:ty,)*) => {
$(
impl ToValue for $into_ty {
fn to_value(&self) -> Value {
Value::from(*self)
}
}

impl<'v> From<$into_ty> for Value<'v> {
fn from(value: $into_ty) -> Self {
Value::from_primitive(value)
}
}
)*
};
}

impl_to_value_primitive![usize, u8, u16, u32, u64, isize, i8, i16, i32, i64, f32, f64, char, bool,];

#[cfg(feature = "std")]
mod std_support {
Expand Down
94 changes: 22 additions & 72 deletions src/kv/value/internal/cast.rs → src/kv/value/internal/cast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@
//! but may end up executing arbitrary caller code if the value is complex.
//! They will also attempt to downcast erased types into a primitive where possible.
use std::any::TypeId;
use std::fmt;

use super::{Erased, Inner, Primitive, Visitor};
use super::{Inner, Primitive, Visitor};
use crate::kv::value::{Error, Value};

mod primitive;

/// Attempt to capture a primitive from some generic value.
///
/// If the value is a primitive type, then cast it here, avoiding needing to erase its value
/// This makes `Value`s produced by `Value::from_*` more useful
pub(super) fn try_from_primitive<'v, T: 'static>(value: &'v T) -> Option<Value<'v>> {
primitive::from_any(value).map(|primitive| Value {
inner: Inner::Primitive(primitive),
})
}

impl<'v> Value<'v> {
/// Try get a `usize` from this value.
///
Expand Down Expand Up @@ -203,8 +214,9 @@ impl<'v> Inner<'v> {
Ok(())
}

fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> {
self.0 = Cast::Primitive(Primitive::Str(v));
#[cfg(feature = "std")]
fn str(&mut self, s: &str) -> Result<(), Error> {
self.0 = Cast::String(s.to_owned());
Ok(())
}

Expand All @@ -213,9 +225,8 @@ impl<'v> Inner<'v> {
Ok(())
}

#[cfg(feature = "std")]
fn str(&mut self, v: &str) -> Result<(), Error> {
self.0 = Cast::String(v.into());
fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> {
self.0 = Cast::Primitive(Primitive::Str(v));
Ok(())
}

Expand All @@ -231,24 +242,14 @@ impl<'v> Inner<'v> {
}
}

// Try downcast an erased value first
// It also lets us avoid the Visitor infrastructure for simple primitives
let primitive = match self {
Inner::Primitive(value) => Some(value),
Inner::Fill(value) => value.downcast_primitive(),
Inner::Debug(value) => value.downcast_primitive(),
Inner::Display(value) => value.downcast_primitive(),

#[cfg(feature = "sval")]
Inner::Sval(value) => value.downcast_primitive(),
};

primitive.map(Cast::Primitive).unwrap_or_else(|| {
if let Inner::Primitive(value) = self {
Cast::Primitive(value)
} else {
// If the erased value isn't a primitive then we visit it
let mut cast = CastVisitor(Cast::Primitive(Primitive::None));
let _ = self.visit(&mut cast);
cast.0
})
}
}
}

Expand Down Expand Up @@ -321,57 +322,6 @@ impl<'v> Primitive<'v> {
}
}

impl<'v, T: ?Sized + 'static> Erased<'v, T> {
// NOTE: This function is a perfect candidate for memoization
// The outcome could be stored in a `Cell<Primitive>`
fn downcast_primitive(self) -> Option<Primitive<'v>> {
macro_rules! type_ids {
($($value:ident : $ty:ty => $cast:expr,)*) => {{
struct TypeIds;

impl TypeIds {
fn downcast_primitive<'v, T: ?Sized>(&self, value: Erased<'v, T>) -> Option<Primitive<'v>> {
$(
if TypeId::of::<$ty>() == value.type_id {
let $value = unsafe { value.downcast_unchecked::<$ty>() };
return Some(Primitive::from($cast));
}
)*

None
}
}

TypeIds
}};
}

let type_ids = type_ids![
value: usize => *value as u64,
value: u8 => *value as u64,
value: u16 => *value as u64,
value: u32 => *value as u64,
value: u64 => *value,

value: isize => *value as i64,
value: i8 => *value as i64,
value: i16 => *value as i64,
value: i32 => *value as i64,
value: i64 => *value,

value: f32 => *value as f64,
value: f64 => *value,

value: char => *value,
value: bool => *value,

value: &str => *value,
];

type_ids.downcast_primitive(self)
}
}

#[cfg(feature = "std")]
mod std_support {
use super::*;
Expand Down
Loading

0 comments on commit 803a23b

Please sign in to comment.