Rework structured value casting (#396)

Also adds a const-fn based mechanism for pulling concrete values out of generic ones
This commit is contained in:
Ashley Mannix 2020-08-03 18:05:15 +10:00 committed by GitHub
parent ca54ac75ce
commit 803a23b15e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 665 additions and 188 deletions

30
benches/value.rs Normal file
View File

@ -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))
}

View File

@ -2,13 +2,72 @@
//! atomics and sets `cfg` flags accordingly. //! atomics and sets `cfg` flags accordingly.
use std::env; 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() { 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") { if !target.starts_with("thumbv6") {
println!("cargo:rustc-cfg=atomic_cas"); 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"); 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()
}

View File

@ -2,17 +2,17 @@
use std::fmt; use std::fmt;
use super::internal::{Erased, Inner, Visitor}; use super::internal::{Inner, Visitor};
use super::{Error, Value}; use super::{Error, Value};
impl<'v> Value<'v> { impl<'v> Value<'v> {
/// Get a value from a fillable slot. /// Get a value from a fillable slot.
pub fn from_fill<T>(value: &'v T) -> Self pub fn from_fill<T>(value: &'v T) -> Self
where where
T: Fill + 'static, T: Fill,
{ {
Value { Value {
inner: Inner::Fill(unsafe { Erased::new_unchecked::<T>(value) }), inner: Inner::Fill(value),
} }
} }
} }

View File

@ -7,24 +7,6 @@ use std::fmt;
use super::{Primitive, ToValue, Value}; 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 { impl<'v> ToValue for &'v str {
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
Value::from(*self) Value::from(*self)
@ -67,25 +49,25 @@ where
} }
} }
impl_into_owned! [ macro_rules! impl_to_value_primitive {
usize => u64, ($($into_ty:ty,)*) => {
u8 => u64, $(
u16 => u64, impl ToValue for $into_ty {
u32 => u64, fn to_value(&self) -> Value {
u64 => u64, Value::from(*self)
}
}
isize => i64, impl<'v> From<$into_ty> for Value<'v> {
i8 => i64, fn from(value: $into_ty) -> Self {
i16 => i64, Value::from_primitive(value)
i32 => i64, }
i64 => i64, }
)*
};
}
f32 => f64, impl_to_value_primitive![usize, u8, u16, u32, u64, isize, i8, i16, i32, i64, f32, f64, char, bool,];
f64 => f64,
char => char,
bool => bool,
];
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod std_support { mod std_support {

View File

@ -4,12 +4,23 @@
//! but may end up executing arbitrary caller code if the value is complex. //! 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. //! They will also attempt to downcast erased types into a primitive where possible.
use std::any::TypeId;
use std::fmt; use std::fmt;
use super::{Erased, Inner, Primitive, Visitor}; use super::{Inner, Primitive, Visitor};
use crate::kv::value::{Error, Value}; 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> { impl<'v> Value<'v> {
/// Try get a `usize` from this value. /// Try get a `usize` from this value.
/// ///
@ -203,8 +214,9 @@ impl<'v> Inner<'v> {
Ok(()) Ok(())
} }
fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> { #[cfg(feature = "std")]
self.0 = Cast::Primitive(Primitive::Str(v)); fn str(&mut self, s: &str) -> Result<(), Error> {
self.0 = Cast::String(s.to_owned());
Ok(()) Ok(())
} }
@ -213,9 +225,8 @@ impl<'v> Inner<'v> {
Ok(()) Ok(())
} }
#[cfg(feature = "std")] fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> {
fn str(&mut self, v: &str) -> Result<(), Error> { self.0 = Cast::Primitive(Primitive::Str(v));
self.0 = Cast::String(v.into());
Ok(()) Ok(())
} }
@ -231,24 +242,14 @@ impl<'v> Inner<'v> {
} }
} }
// Try downcast an erased value first if let Inner::Primitive(value) = self {
// It also lets us avoid the Visitor infrastructure for simple primitives Cast::Primitive(value)
let primitive = match self { } else {
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 the erased value isn't a primitive then we visit it // If the erased value isn't a primitive then we visit it
let mut cast = CastVisitor(Cast::Primitive(Primitive::None)); let mut cast = CastVisitor(Cast::Primitive(Primitive::None));
let _ = self.visit(&mut cast); let _ = self.visit(&mut cast);
cast.0 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<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")] #[cfg(feature = "std")]
mod std_support { mod std_support {
use super::*; use super::*;

View File

@ -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<crate::kv::value::internal::Primitive<'v>> {
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<Primitive> = {
$(
const $const_ident: TypeId = TypeId::of::<$ty>();
const $option_ident: TypeId = TypeId::of::<Option<$ty>>();
);*
match TypeId::of::<Self>() {
$(
$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<Primitive> {
(Self::CALL)(self)
}
}
impl<T: ?Sized + 'static> 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<crate::kv::value::internal::Primitive<'v>> {
// 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::<Option<$ty>>(),
stringify!(
(
std::any::TypeId::of::<Option<$ty>>(),
(|value| unsafe {
debug_assert_eq!(value.type_id(), std::any::TypeId::of::<Option<$ty>>());
// 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();
}

View File

@ -5,28 +5,54 @@
use std::fmt; use std::fmt;
use super::{Erased, Inner, Visitor}; use super::{cast, Inner, Visitor};
use crate::kv; use crate::kv;
use crate::kv::value::{Error, Slot}; use crate::kv::value::{Error, Slot, ToValue};
impl<'v> kv::Value<'v> { impl<'v> kv::Value<'v> {
/// Get a value from a debuggable type. /// Get a value from a debuggable type.
pub fn from_debug<T>(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<T>(value: &'v T) -> Self
where where
T: fmt::Debug + 'static, T: fmt::Debug + 'static,
{ {
kv::Value { cast::try_from_primitive(value).unwrap_or(kv::Value {
inner: Inner::Debug(unsafe { Erased::new_unchecked::<T>(value) }), inner: Inner::Debug(value),
})
} }
/// Get a value from a debuggable type.
pub fn from_debug<T>(value: &'v T) -> Self
where
T: fmt::Debug,
{
kv::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<T>(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. /// Get a value from a displayable type.
pub fn from_display<T>(value: &'v T) -> Self pub fn from_display<T>(value: &'v T) -> Self
where where
T: fmt::Display + 'static, T: fmt::Display,
{ {
kv::Value { kv::Value {
inner: Inner::Display(unsafe { Erased::new_unchecked::<T>(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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use kv::value::test::Token;
use crate::kv::value::ToValue; 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] #[test]
fn fmt_cast() { fn fmt_cast() {
assert_eq!( assert_eq!(
42u32, 42u32,
kv::Value::from_debug(&42u64) kv::Value::capture_debug(&42u64)
.to_u32() .to_u32()
.expect("invalid value") .expect("invalid value")
); );
assert_eq!( assert_eq!(
"a string", "a string",
kv::Value::from_display(&"a string") kv::Value::capture_display(&"a string")
.to_borrowed_str() .to_borrowed_str()
.expect("invalid value") .expect("invalid value")
); );

View File

@ -3,8 +3,6 @@
//! This implementation isn't intended to be public. It may need to change //! This implementation isn't intended to be public. It may need to change
//! for optimizations or to support new external serialization frameworks. //! for optimizations or to support new external serialization frameworks.
use std::any::TypeId;
use super::{Error, Fill, Slot}; use super::{Error, Fill, Slot};
pub(super) mod cast; pub(super) mod cast;
@ -18,27 +16,29 @@ pub(super) enum Inner<'v> {
/// A simple primitive value that can be copied without allocating. /// A simple primitive value that can be copied without allocating.
Primitive(Primitive<'v>), Primitive(Primitive<'v>),
/// A value that can be filled. /// A value that can be filled.
Fill(Erased<'v, dyn Fill + 'static>), Fill(&'v (dyn Fill)),
/// A debuggable value. /// A debuggable value.
Debug(Erased<'v, dyn fmt::Debug + 'static>), Debug(&'v (dyn fmt::Debug)),
/// A displayable value. /// A displayable value.
Display(Erased<'v, dyn fmt::Display + 'static>), Display(&'v (dyn fmt::Display)),
#[cfg(feature = "kv_unstable_sval")] #[cfg(feature = "kv_unstable_sval")]
/// A structured value from `sval`. /// A structured value from `sval`.
Sval(Erased<'v, dyn sval::Value + 'static>), Sval(&'v (dyn sval::Value)),
} }
impl<'v> Inner<'v> { impl<'v> Inner<'v> {
pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> { pub(super) fn visit(self, visitor: &mut dyn Visitor<'v>) -> Result<(), Error> {
match self { match self {
Inner::Primitive(value) => value.visit(visitor), Inner::Primitive(value) => value.visit(visitor),
Inner::Fill(value) => value.get().fill(&mut Slot::new(visitor)),
Inner::Debug(value) => visitor.debug(value.get()), Inner::Debug(value) => visitor.debug(value),
Inner::Display(value) => visitor.display(value.get()), Inner::Display(value) => visitor.display(value),
Inner::Fill(value) => value.fill(&mut Slot::new(visitor)),
#[cfg(feature = "kv_unstable_sval")] #[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<u8> for Primitive<'v> {
#[inline]
fn from(v: u8) -> Self {
Primitive::Unsigned(v as u64)
}
}
impl<'v> From<u16> for Primitive<'v> {
#[inline]
fn from(v: u16) -> Self {
Primitive::Unsigned(v as u64)
}
}
impl<'v> From<u32> for Primitive<'v> {
#[inline]
fn from(v: u32) -> Self {
Primitive::Unsigned(v as u64)
}
}
impl<'v> From<u64> for Primitive<'v> { impl<'v> From<u64> for Primitive<'v> {
#[inline]
fn from(v: u64) -> Self { fn from(v: u64) -> Self {
Primitive::Unsigned(v) Primitive::Unsigned(v)
} }
} }
impl<'v> From<usize> for Primitive<'v> {
#[inline]
fn from(v: usize) -> Self {
Primitive::Unsigned(v as u64)
}
}
impl<'v> From<i8> for Primitive<'v> {
#[inline]
fn from(v: i8) -> Self {
Primitive::Signed(v as i64)
}
}
impl<'v> From<i16> for Primitive<'v> {
#[inline]
fn from(v: i16) -> Self {
Primitive::Signed(v as i64)
}
}
impl<'v> From<i32> for Primitive<'v> {
#[inline]
fn from(v: i32) -> Self {
Primitive::Signed(v as i64)
}
}
impl<'v> From<i64> for Primitive<'v> { impl<'v> From<i64> for Primitive<'v> {
#[inline]
fn from(v: i64) -> Self { fn from(v: i64) -> Self {
Primitive::Signed(v) Primitive::Signed(v)
} }
} }
impl<'v> From<isize> for Primitive<'v> {
#[inline]
fn from(v: isize) -> Self {
Primitive::Signed(v as i64)
}
}
impl<'v> From<f32> for Primitive<'v> {
#[inline]
fn from(v: f32) -> Self {
Primitive::Float(v as f64)
}
}
impl<'v> From<f64> for Primitive<'v> { impl<'v> From<f64> for Primitive<'v> {
#[inline]
fn from(v: f64) -> Self { fn from(v: f64) -> Self {
Primitive::Float(v) Primitive::Float(v)
} }
} }
impl<'v> From<bool> for Primitive<'v> { impl<'v> From<bool> for Primitive<'v> {
#[inline]
fn from(v: bool) -> Self { fn from(v: bool) -> Self {
Primitive::Bool(v) Primitive::Bool(v)
} }
} }
impl<'v> From<char> for Primitive<'v> { impl<'v> From<char> for Primitive<'v> {
#[inline]
fn from(v: char) -> Self { fn from(v: char) -> Self {
Primitive::Char(v) Primitive::Char(v)
} }
} }
impl<'v> From<&'v str> for Primitive<'v> { impl<'v> From<&'v str> for Primitive<'v> {
#[inline]
fn from(v: &'v str) -> Self { fn from(v: &'v str) -> Self {
Primitive::Str(v) Primitive::Str(v)
} }
} }
impl<'v> From<fmt::Arguments<'v>> for Primitive<'v> { impl<'v> From<fmt::Arguments<'v>> for Primitive<'v> {
#[inline]
fn from(v: fmt::Arguments<'v>) -> Self { fn from(v: fmt::Arguments<'v>) -> Self {
Primitive::Fmt(v) 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<T>` 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<U>(inner: &'v T) -> Self
where
U: 'static,
T: 'static,
{
Erased {
type_id: TypeId::of::<U>(),
inner,
}
}
pub(super) fn get(self) -> &'v T {
self.inner
}
// SAFETY: The underlying type of `T` is `U`
pub(super) unsafe fn downcast_unchecked<U>(self) -> &'v U {
&*(self.inner as *const T as *const U)
}
}

View File

@ -7,19 +7,32 @@ extern crate sval;
use std::fmt; use std::fmt;
use super::cast::Cast; use super::cast::{self, Cast};
use super::{Erased, Inner, Primitive, Visitor}; use super::{Inner, Primitive, Visitor};
use crate::kv; use crate::kv;
use crate::kv::value::{Error, Slot}; use crate::kv::value::{Error, Slot, ToValue};
impl<'v> kv::Value<'v> { impl<'v> kv::Value<'v> {
/// Get a value from a structured type. /// Get a value from a structured type.
pub fn from_sval<T>(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<T>(value: &'v T) -> Self
where where
T: sval::Value + 'static, 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<T>(value: &'v T) -> Self
where
T: sval::Value,
{ {
kv::Value { kv::Value {
inner: Inner::Sval(unsafe { Erased::new_unchecked::<T>(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(in kv::value) use self::sval::Value;
pub(super) fn fmt(f: &mut fmt::Formatter, v: &dyn sval::Value) -> Result<(), Error> { 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; use kv::value::test::Token;
#[test] #[test]
fn test_from_sval() { fn sval_capture() {
assert_eq!(kv::Value::from_sval(&42u64).to_token(), Token::Sval); assert_eq!(kv::Value::capture_sval(&42u64).to_token(), Token::U64(42));
}
#[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);
} }
#[test] #[test]
fn sval_cast() { fn sval_cast() {
assert_eq!( assert_eq!(
42u32, 42u32,
kv::Value::from_sval(&42u64) kv::Value::capture_sval(&42u64)
.to_u32() .to_u32()
.expect("invalid value") .expect("invalid value")
); );
assert_eq!( assert_eq!(
"a string", "a string",
kv::Value::from_sval(&"a string") kv::Value::capture_sval(&"a string")
.to_borrowed_str() .to_borrowed_str()
.expect("invalid value") .expect("invalid value")
); );
@ -186,12 +205,20 @@ mod tests {
#[cfg(feature = "std")] #[cfg(feature = "std")]
assert_eq!( assert_eq!(
"a string", "a string",
kv::Value::from_sval(&"a string") kv::Value::capture_sval(&"a string")
.to_str() .to_str()
.expect("invalid value") .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] #[test]
fn sval_debug() { fn sval_debug() {
struct TestSval; struct TestSval;
@ -207,4 +234,19 @@ mod tests {
format!("{:04?}", kv::Value::from_sval(&TestSval)), 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")
);
}
}
} }

View File

@ -34,11 +34,135 @@ impl<'v> ToValue for Value<'v> {
} }
/// A value in a structured key-value pair. /// 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> { pub struct Value<'v> {
inner: Inner<'v>, inner: Inner<'v>,
} }
impl<'v> Value<'v> { impl<'v> Value<'v> {
/// Get a value from a type implementing `ToValue`.
pub fn from_any<T>(value: &'v T) -> Self
where
T: ToValue,
{
value.to_value()
}
/// Get a value from an internal primitive. /// Get a value from an internal primitive.
fn from_primitive<T>(value: T) -> Self fn from_primitive<T>(value: T) -> Self
where where