Skip to content

Rework structured value casting #396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0459fd0
capture primitives in from_debug and from_display calls
KodrAus May 22, 2020
be90b6e
experiment with different strategies for lookups
KodrAus May 22, 2020
ef5778b
wire up build-time type id sorting
KodrAus May 22, 2020
75211b5
refactor type id generation to encapsulate details
KodrAus May 22, 2020
dac6e30
remove dead code
KodrAus May 22, 2020
1f59a35
refactor casting a bit and fix sval
KodrAus May 26, 2020
7af0cf1
shift some docs
KodrAus May 26, 2020
c00c8aa
explicit From impls for dyn traits
KodrAus May 26, 2020
690ae53
remove junk file
KodrAus May 26, 2020
dd15f5b
remove an invalid ;
KodrAus May 26, 2020
256b962
const works out the same as static
KodrAus May 26, 2020
fd2049a
remove 'static bounds where unneeded
KodrAus Jun 3, 2020
c8a3d37
add const version
KodrAus Jun 3, 2020
f9333fb
add specialization-based conversion
KodrAus Jun 3, 2020
751ea85
add some notes about Value construction
KodrAus Jun 3, 2020
b265bdc
add a from_any method to avoid trait imports
KodrAus Jun 3, 2020
a022108
remove implied Sized bound
KodrAus Jun 4, 2020
117b7a8
tidy up type id checking
KodrAus Jul 1, 2020
d0f6561
flesh out const eval based conversions
KodrAus Jul 1, 2020
34d4a49
Merge branch 'master' of https://github.com/rust-lang/log into fix/va…
KodrAus Jul 1, 2020
52ddb2e
get doc tests to pass
KodrAus Jul 1, 2020
84a3a7b
run fmt
KodrAus Jul 1, 2020
0628fe6
remove unneeded unstable attribute
KodrAus Jul 30, 2020
023e7e5
const type ids needs 1.47
KodrAus Jul 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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