diff --git a/benches/value.rs b/benches/value.rs new file mode 100644 index 0000000..4e6960e --- /dev/null +++ b/benches/value.rs @@ -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)) +} diff --git a/build.rs b/build.rs index 6717bf0..40d1612 100644 --- a/build.rs +++ b/build.rs @@ -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 { + env::var("TARGET").ok() +} + +// From the `serde` build script +fn rustc_minor_version() -> Option { + 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() +} diff --git a/src/kv/value/fill.rs b/src/kv/value/fill.rs index d14a642..f34ba31 100644 --- a/src/kv/value/fill.rs +++ b/src/kv/value/fill.rs @@ -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(value: &'v T) -> Self where - T: Fill + 'static, + T: Fill, { Value { - inner: Inner::Fill(unsafe { Erased::new_unchecked::(value) }), + inner: Inner::Fill(value), } } } diff --git a/src/kv/value/impls.rs b/src/kv/value/impls.rs index a6169f1..506128b 100644 --- a/src/kv/value/impls.rs +++ b/src/kv/value/impls.rs @@ -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) @@ -67,25 +49,25 @@ where } } -impl_into_owned! [ - usize => u64, - u8 => u64, - u16 => u64, - u32 => u64, - u64 => u64, +macro_rules! impl_to_value_primitive { + ($($into_ty:ty,)*) => { + $( + impl ToValue for $into_ty { + fn to_value(&self) -> Value { + Value::from(*self) + } + } - isize => i64, - i8 => i64, - i16 => i64, - i32 => i64, - i64 => i64, + impl<'v> From<$into_ty> for Value<'v> { + fn from(value: $into_ty) -> Self { + Value::from_primitive(value) + } + } + )* + }; +} - f32 => f64, - f64 => f64, - - char => char, - bool => bool, -]; +impl_to_value_primitive![usize, u8, u16, u32, u64, isize, i8, i16, i32, i64, f32, f64, char, bool,]; #[cfg(feature = "std")] mod std_support { diff --git a/src/kv/value/internal/cast.rs b/src/kv/value/internal/cast/mod.rs similarity index 83% rename from src/kv/value/internal/cast.rs rename to src/kv/value/internal/cast/mod.rs index d2aa86e..ec6a223 100644 --- a/src/kv/value/internal/cast.rs +++ b/src/kv/value/internal/cast/mod.rs @@ -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> { + primitive::from_any(value).map(|primitive| Value { + inner: Inner::Primitive(primitive), + }) +} + impl<'v> Value<'v> { /// Try get a `usize` from this value. /// @@ -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(()) } @@ -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(()) } @@ -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 - }) + } } } @@ -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` - fn downcast_primitive(self) -> Option> { - 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> { - $( - 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::*; diff --git a/src/kv/value/internal/cast/primitive.rs b/src/kv/value/internal/cast/primitive.rs new file mode 100644 index 0000000..991a700 --- /dev/null +++ b/src/kv/value/internal/cast/primitive.rs @@ -0,0 +1,195 @@ +/* +This module generates code to try efficiently convert some arbitrary `T: 'static` into +a `Primitive`. It's used by both the `build.rs` script and by the source itself. There +are currently two implementations here: + +- When the compiler version is less than `1.46.0` we check type ids at runtime. This +means generating a pre-sorted list of type ids at compile time using the `build.rs` +and matching them at runtime. +- When the compiler version is at least `1.46.0` we use const evaluation to check type ids +at compile time. There's no generated code from `build.rs` involved. + +In the future when `min_specialization` is stabilized we could use it instead and avoid needing +the `'static` bound altogether. +*/ + +// Use consts to match a type with a conversion fn +#[cfg(all(srcbuild, const_type_id))] +pub(super) fn from_any<'v, T: ?Sized + 'static>( + value: &'v T, +) -> Option> { + use std::any::TypeId; + + use crate::kv::value::internal::Primitive; + + macro_rules! to_primitive { + ($($ty:ty : ($const_ident:ident, $option_ident:ident),)*) => { + trait ToPrimitive + where + Self: 'static, + { + const CALL: fn(&Self) -> Option = { + $( + const $const_ident: TypeId = TypeId::of::<$ty>(); + const $option_ident: TypeId = TypeId::of::>(); + );* + + match TypeId::of::() { + $( + $const_ident => |v| Some(Primitive::from(unsafe { *(v as *const Self as *const $ty) })), + $option_ident => |v| Some({ + let v = unsafe { *(v as *const Self as *const Option<$ty>) }; + match v { + Some(v) => Primitive::from(v), + None => Primitive::None, + } + }), + )* + + _ => |_| None, + } + }; + + fn to_primitive(&self) -> Option { + (Self::CALL)(self) + } + } + + impl ToPrimitive for T {} + } + } + + // NOTE: The types here *must* match the ones used below when `const_type_id` is not available + to_primitive![ + usize: (USIZE, OPTION_USIZE), + u8: (U8, OPTION_U8), + u16: (U16, OPTION_U16), + u32: (U32, OPTION_U32), + u64: (U64, OPTION_U64), + + isize: (ISIZE, OPTION_ISIZE), + i8: (I8, OPTION_I8), + i16: (I16, OPTION_I16), + i32: (I32, OPTION_I32), + i64: (I64, OPTION_I64), + + f32: (F32, OPTION_F32), + f64: (F64, OPTION_F64), + + char: (CHAR, OPTION_CHAR), + bool: (BOOL, OPTION_BOOL), + &'static str: (STR, OPTION_STR), + ]; + + value.to_primitive() +} + +#[cfg(all(not(src_build), const_type_id))] +#[allow(dead_code)] +pub fn generate() {} + +// Use a build-time generated set of type ids to match a type with a conversion fn +#[cfg(all(srcbuild, not(const_type_id)))] +pub(super) fn from_any<'v>( + value: &'v (dyn std::any::Any + 'static), +) -> Option> { + // The set of type ids that map to primitives are generated at build-time + // by the contents of `sorted_type_ids.expr`. These type ids are pre-sorted + // so that they can be searched efficiently. See the `sorted_type_ids.expr.rs` + // file for the set of types that appear in this list + let type_ids = include!(concat!(env!("OUT_DIR"), "/into_primitive.rs")); + + if let Ok(i) = type_ids.binary_search_by_key(&value.type_id(), |&(k, _)| k) { + Some((type_ids[i].1)(value)) + } else { + None + } +} + +// When the `src_build` config is not set then we're in the build script +#[cfg(all(not(srcbuild), not(const_type_id)))] +#[allow(dead_code)] +pub fn generate() { + use std::path::Path; + use std::{env, fs}; + + macro_rules! type_ids { + ($($ty:ty,)*) => { + [ + $( + ( + std::any::TypeId::of::<$ty>(), + stringify!( + ( + std::any::TypeId::of::<$ty>(), + (|value| unsafe { + debug_assert_eq!(value.type_id(), std::any::TypeId::of::<$ty>()); + + // SAFETY: We verify the value is $ty before casting + let value = *(value as *const dyn std::any::Any as *const $ty); + crate::kv::value::internal::Primitive::from(value) + }) as for<'a> fn(&'a (dyn std::any::Any + 'static)) -> crate::kv::value::internal::Primitive<'a> + ) + ) + ), + )* + $( + ( + std::any::TypeId::of::>(), + stringify!( + ( + std::any::TypeId::of::>(), + (|value| unsafe { + debug_assert_eq!(value.type_id(), std::any::TypeId::of::>()); + + // SAFETY: We verify the value is Option<$ty> before casting + let value = *(value as *const dyn std::any::Any as *const Option<$ty>); + if let Some(value) = value { + crate::kv::value::internal::Primitive::from(value) + } else { + crate::kv::value::internal::Primitive::None + } + }) as for<'a> fn(&'a (dyn std::any::Any + 'static)) -> crate::kv::value::internal::Primitive<'a> + ) + ) + ), + )* + ] + }; + } + + // NOTE: The types here *must* match the ones used above when `const_type_id` is available + let mut type_ids = type_ids![ + usize, + u8, + u16, + u32, + u64, + isize, + i8, + i16, + i32, + i64, + f32, + f64, + char, + bool, + &'static str, + ]; + + type_ids.sort_by_key(|&(k, _)| k); + + let mut ordered_type_ids_expr = String::new(); + + ordered_type_ids_expr.push('['); + + for (_, v) in &type_ids { + ordered_type_ids_expr.push_str(v); + ordered_type_ids_expr.push(','); + } + + ordered_type_ids_expr.push(']'); + + let path = Path::new(&env::var_os("OUT_DIR").unwrap()).join("into_primitive.rs"); + fs::write(path, ordered_type_ids_expr).unwrap(); +} diff --git a/src/kv/value/internal/fmt.rs b/src/kv/value/internal/fmt.rs index eabd4dd..bde703c 100644 --- a/src/kv/value/internal/fmt.rs +++ b/src/kv/value/internal/fmt.rs @@ -5,28 +5,54 @@ use std::fmt; -use super::{Erased, Inner, Visitor}; +use super::{cast, Inner, Visitor}; use crate::kv; -use crate::kv::value::{Error, Slot}; +use crate::kv::value::{Error, Slot, ToValue}; impl<'v> kv::Value<'v> { /// Get a value from a debuggable type. - pub fn from_debug(value: &'v T) -> Self + /// + /// This method will attempt to capture the given value as a well-known primitive + /// before resorting to using its `Debug` implementation. + pub fn capture_debug(value: &'v T) -> Self where T: fmt::Debug + 'static, + { + cast::try_from_primitive(value).unwrap_or(kv::Value { + inner: Inner::Debug(value), + }) + } + + /// Get a value from a debuggable type. + pub fn from_debug(value: &'v T) -> Self + where + T: fmt::Debug, { kv::Value { - inner: Inner::Debug(unsafe { Erased::new_unchecked::(value) }), + inner: Inner::Debug(value), } } + /// Get a value from a displayable type. + /// + /// This method will attempt to capture the given value as a well-known primitive + /// before resorting to using its `Display` implementation. + pub fn capture_display(value: &'v T) -> Self + where + T: fmt::Display + 'static, + { + cast::try_from_primitive(value).unwrap_or(kv::Value { + inner: Inner::Display(value), + }) + } + /// Get a value from a displayable type. pub fn from_display(value: &'v T) -> Self where - T: fmt::Display + 'static, + T: fmt::Display, { kv::Value { - inner: Inner::Display(unsafe { Erased::new_unchecked::(value) }), + inner: Inner::Display(value), } } } @@ -201,24 +227,64 @@ impl<'v> fmt::Display for kv::Value<'v> { } } +impl<'v> ToValue for dyn fmt::Debug + 'v { + fn to_value(&self) -> kv::Value { + kv::Value::from(self) + } +} + +impl<'v> ToValue for dyn fmt::Display + 'v { + fn to_value(&self) -> kv::Value { + kv::Value::from(self) + } +} + +impl<'v> From<&'v (dyn fmt::Debug)> for kv::Value<'v> { + fn from(value: &'v (dyn fmt::Debug)) -> kv::Value<'v> { + kv::Value { + inner: Inner::Debug(value), + } + } +} + +impl<'v> From<&'v (dyn fmt::Display)> for kv::Value<'v> { + fn from(value: &'v (dyn fmt::Display)) -> kv::Value<'v> { + kv::Value { + inner: Inner::Display(value), + } + } +} + #[cfg(test)] mod tests { use super::*; + use kv::value::test::Token; use crate::kv::value::ToValue; + #[test] + fn fmt_capture() { + assert_eq!(kv::Value::capture_debug(&1u16).to_token(), Token::U64(1)); + assert_eq!(kv::Value::capture_display(&1u16).to_token(), Token::U64(1)); + + assert_eq!( + kv::Value::capture_debug(&Some(1u16)).to_token(), + Token::U64(1) + ); + } + #[test] fn fmt_cast() { assert_eq!( 42u32, - kv::Value::from_debug(&42u64) + kv::Value::capture_debug(&42u64) .to_u32() .expect("invalid value") ); assert_eq!( "a string", - kv::Value::from_display(&"a string") + kv::Value::capture_display(&"a string") .to_borrowed_str() .expect("invalid value") ); diff --git a/src/kv/value/internal/mod.rs b/src/kv/value/internal/mod.rs index 429f0db..4acbe68 100644 --- a/src/kv/value/internal/mod.rs +++ b/src/kv/value/internal/mod.rs @@ -3,8 +3,6 @@ //! This implementation isn't intended to be public. It may need to change //! for optimizations or to support new external serialization frameworks. -use std::any::TypeId; - use super::{Error, Fill, Slot}; pub(super) mod cast; @@ -18,27 +16,29 @@ pub(super) enum Inner<'v> { /// A simple primitive value that can be copied without allocating. Primitive(Primitive<'v>), /// A value that can be filled. - Fill(Erased<'v, dyn Fill + 'static>), + Fill(&'v (dyn Fill)), /// A debuggable value. - Debug(Erased<'v, dyn fmt::Debug + 'static>), + Debug(&'v (dyn fmt::Debug)), /// A displayable value. - Display(Erased<'v, dyn fmt::Display + 'static>), + Display(&'v (dyn fmt::Display)), #[cfg(feature = "kv_unstable_sval")] /// A structured value from `sval`. - Sval(Erased<'v, dyn sval::Value + 'static>), + Sval(&'v (dyn sval::Value)), } impl<'v> Inner<'v> { pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { match self { Inner::Primitive(value) => value.visit(visitor), - Inner::Fill(value) => value.get().fill(&mut Slot::new(visitor)), - Inner::Debug(value) => visitor.debug(value.get()), - Inner::Display(value) => visitor.display(value.get()), + + Inner::Debug(value) => visitor.debug(value), + Inner::Display(value) => visitor.display(value), + + Inner::Fill(value) => value.fill(&mut Slot::new(visitor)), #[cfg(feature = "kv_unstable_sval")] - Inner::Sval(value) => visitor.sval(value.get()), + Inner::Sval(value) => visitor.sval(value), } } } @@ -97,85 +97,114 @@ impl<'v> Primitive<'v> { } } +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: u8) -> Self { + Primitive::Unsigned(v as u64) + } +} + +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: u16) -> Self { + Primitive::Unsigned(v as u64) + } +} + +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: u32) -> Self { + Primitive::Unsigned(v as u64) + } +} + impl<'v> From for Primitive<'v> { + #[inline] fn from(v: u64) -> Self { Primitive::Unsigned(v) } } +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: usize) -> Self { + Primitive::Unsigned(v as u64) + } +} + +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: i8) -> Self { + Primitive::Signed(v as i64) + } +} + +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: i16) -> Self { + Primitive::Signed(v as i64) + } +} + +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: i32) -> Self { + Primitive::Signed(v as i64) + } +} + impl<'v> From for Primitive<'v> { + #[inline] fn from(v: i64) -> Self { Primitive::Signed(v) } } +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: isize) -> Self { + Primitive::Signed(v as i64) + } +} + +impl<'v> From for Primitive<'v> { + #[inline] + fn from(v: f32) -> Self { + Primitive::Float(v as f64) + } +} + impl<'v> From for Primitive<'v> { + #[inline] fn from(v: f64) -> Self { Primitive::Float(v) } } impl<'v> From for Primitive<'v> { + #[inline] fn from(v: bool) -> Self { Primitive::Bool(v) } } impl<'v> From for Primitive<'v> { + #[inline] fn from(v: char) -> Self { Primitive::Char(v) } } impl<'v> From<&'v str> for Primitive<'v> { + #[inline] fn from(v: &'v str) -> Self { Primitive::Str(v) } } impl<'v> From> for Primitive<'v> { + #[inline] fn from(v: fmt::Arguments<'v>) -> Self { Primitive::Fmt(v) } } - -/// A downcastable dynamic type. -pub(super) struct Erased<'v, T: ?Sized> { - type_id: TypeId, - inner: &'v T, -} - -impl<'v, T: ?Sized> Clone for Erased<'v, T> { - fn clone(&self) -> Self { - Erased { - type_id: self.type_id, - inner: self.inner, - } - } -} - -impl<'v, T: ?Sized> Copy for Erased<'v, T> {} - -impl<'v, T: ?Sized> Erased<'v, T> { - // SAFETY: `U: Unsize` and the underlying value `T` must not change - // We could add a safe variant of this method with the `Unsize` trait - pub(super) unsafe fn new_unchecked(inner: &'v T) -> Self - where - U: 'static, - T: 'static, - { - Erased { - type_id: TypeId::of::(), - inner, - } - } - - pub(super) fn get(self) -> &'v T { - self.inner - } - - // SAFETY: The underlying type of `T` is `U` - pub(super) unsafe fn downcast_unchecked(self) -> &'v U { - &*(self.inner as *const T as *const U) - } -} diff --git a/src/kv/value/internal/sval.rs b/src/kv/value/internal/sval.rs index 0d76f4e..252efd1 100644 --- a/src/kv/value/internal/sval.rs +++ b/src/kv/value/internal/sval.rs @@ -7,19 +7,32 @@ extern crate sval; use std::fmt; -use super::cast::Cast; -use super::{Erased, Inner, Primitive, Visitor}; +use super::cast::{self, Cast}; +use super::{Inner, Primitive, Visitor}; use crate::kv; -use crate::kv::value::{Error, Slot}; +use crate::kv::value::{Error, Slot, ToValue}; impl<'v> kv::Value<'v> { /// Get a value from a structured type. - pub fn from_sval(value: &'v T) -> Self + /// + /// This method will attempt to capture the given value as a well-known primitive + /// before resorting to using its `Value` implementation. + pub fn capture_sval(value: &'v T) -> Self where T: sval::Value + 'static, + { + cast::try_from_primitive(value).unwrap_or(kv::Value { + inner: Inner::Sval(value), + }) + } + + /// Get a value from a structured type. + pub fn from_sval(value: &'v T) -> Self + where + T: sval::Value, { kv::Value { - inner: Inner::Sval(unsafe { Erased::new_unchecked::(value) }), + inner: Inner::Sval(value), } } } @@ -90,6 +103,20 @@ impl<'v> sval::Value for kv::Value<'v> { } } +impl<'v> ToValue for dyn sval::Value + 'v { + fn to_value(&self) -> kv::Value { + kv::Value::from(self) + } +} + +impl<'v> From<&'v (dyn sval::Value)> for kv::Value<'v> { + fn from(value: &'v (dyn sval::Value)) -> kv::Value<'v> { + kv::Value { + inner: Inner::Sval(value), + } + } +} + pub(in kv::value) use self::sval::Value; pub(super) fn fmt(f: &mut fmt::Formatter, v: &dyn sval::Value) -> Result<(), Error> { @@ -155,30 +182,22 @@ mod tests { use kv::value::test::Token; #[test] - fn test_from_sval() { - assert_eq!(kv::Value::from_sval(&42u64).to_token(), Token::Sval); - } - - #[test] - fn test_sval_structured() { - let value = kv::Value::from(42u64); - let expected = vec![sval::test::Token::Unsigned(42)]; - - assert_eq!(sval::test::tokens(value), expected); + fn sval_capture() { + assert_eq!(kv::Value::capture_sval(&42u64).to_token(), Token::U64(42)); } #[test] fn sval_cast() { assert_eq!( 42u32, - kv::Value::from_sval(&42u64) + kv::Value::capture_sval(&42u64) .to_u32() .expect("invalid value") ); assert_eq!( "a string", - kv::Value::from_sval(&"a string") + kv::Value::capture_sval(&"a string") .to_borrowed_str() .expect("invalid value") ); @@ -186,12 +205,20 @@ mod tests { #[cfg(feature = "std")] assert_eq!( "a string", - kv::Value::from_sval(&"a string") + kv::Value::capture_sval(&"a string") .to_str() .expect("invalid value") ); } + #[test] + fn sval_structured() { + let value = kv::Value::from(42u64); + let expected = vec![sval::test::Token::Unsigned(42)]; + + assert_eq!(sval::test::tokens(value), expected); + } + #[test] fn sval_debug() { struct TestSval; @@ -207,4 +234,19 @@ mod tests { format!("{:04?}", kv::Value::from_sval(&TestSval)), ); } + + #[cfg(feature = "std")] + mod std_support { + use super::*; + + #[test] + fn sval_cast() { + assert_eq!( + "a string", + kv::Value::capture_sval(&"a string".to_owned()) + .to_str() + .expect("invalid value") + ); + } + } } diff --git a/src/kv/value/mod.rs b/src/kv/value/mod.rs index 422adb0..8d82826 100644 --- a/src/kv/value/mod.rs +++ b/src/kv/value/mod.rs @@ -34,11 +34,135 @@ impl<'v> ToValue for Value<'v> { } /// A value in a structured key-value pair. +/// +/// # Capturing values +/// +/// There are a few ways to capture a value: +/// +/// - Using the `Value::capture_*` methods. +/// - Using the `Value::from_*` methods. +/// - Using the `ToValue` trait. +/// - Using the standard `From` trait. +/// - Using the `Fill` API. +/// +/// ## Using the `Value::capture_*` methods +/// +/// `Value` offers a few constructor methods that capture values of different kinds. +/// These methods require a `T: 'static` to support downcasting. +/// +/// ``` +/// use log::kv::Value; +/// +/// let value = Value::capture_debug(&42i32); +/// +/// assert_eq!(Some(42), value.to_i32()); +/// ``` +/// +/// ## Using the `Value::from_*` methods +/// +/// `Value` offers a few constructor methods that capture values of different kinds. +/// These methods don't require `T: 'static`, but can't support downcasting. +/// +/// ``` +/// use log::kv::Value; +/// +/// let value = Value::from_debug(&42i32); +/// +/// assert_eq!(None, value.to_i32()); +/// ``` +/// +/// ## Using the `ToValue` trait +/// +/// The `ToValue` trait can be used to capture values generically. +/// It's the bound used by `Source`. +/// +/// ``` +/// # use log::kv::ToValue; +/// let value = 42i32.to_value(); +/// +/// assert_eq!(Some(42), value.to_i32()); +/// ``` +/// +/// ``` +/// # use std::fmt::Debug; +/// use log::kv::ToValue; +/// +/// let value = (&42i32 as &dyn Debug).to_value(); +/// +/// assert_eq!(None, value.to_i32()); +/// ``` +/// +/// ## Using the standard `From` trait +/// +/// Standard types that implement `ToValue` also implement `From`. +/// +/// ``` +/// use log::kv::Value; +/// +/// let value = Value::from(42i32); +/// +/// assert_eq!(Some(42), value.to_i32()); +/// ``` +/// +/// ``` +/// # use std::fmt::Debug; +/// use log::kv::Value; +/// +/// let value = Value::from(&42i32 as &dyn Debug); +/// +/// assert_eq!(None, value.to_i32()); +/// ``` +/// +/// ## Using the `Fill` API +/// +/// The `Fill` trait is a way to bridge APIs that may not be directly +/// compatible with other constructor methods. +/// +/// ``` +/// use log::kv::value::{Value, Slot, Fill, Error}; +/// +/// struct FillSigned; +/// +/// impl Fill for FillSigned { +/// fn fill(&self, slot: &mut Slot) -> Result<(), Error> { +/// slot.fill_any(42i32) +/// } +/// } +/// +/// let value = Value::from_fill(&FillSigned); +/// +/// assert_eq!(Some(42), value.to_i32()); +/// ``` +/// +/// ``` +/// # use std::fmt::Debug; +/// use log::kv::value::{Value, Slot, Fill, Error}; +/// +/// struct FillDebug; +/// +/// impl Fill for FillDebug { +/// fn fill(&self, slot: &mut Slot) -> Result<(), Error> { +/// slot.fill_debug(&42i32 as &dyn Debug) +/// } +/// } +/// +/// let value = Value::from_fill(&FillDebug); +/// +/// assert_eq!(None, value.to_i32()); +/// ``` pub struct Value<'v> { inner: Inner<'v>, } impl<'v> Value<'v> { + /// Get a value from a type implementing `ToValue`. + pub fn from_any(value: &'v T) -> Self + where + T: ToValue, + { + value.to_value() + } + /// Get a value from an internal primitive. fn from_primitive(value: T) -> Self where