mirror of
https://gitee.com/openharmony/third_party_rust_log
synced 2024-11-23 07:40:08 +00:00
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:
parent
ca54ac75ce
commit
803a23b15e
30
benches/value.rs
Normal file
30
benches/value.rs
Normal 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))
|
||||
}
|
61
build.rs
61
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<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()
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
///
|
||||
@ -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<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::*;
|
195
src/kv/value/internal/cast/primitive.rs
Normal file
195
src/kv/value/internal/cast/primitive.rs
Normal 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();
|
||||
}
|
@ -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<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
|
||||
T: fmt::Debug + 'static,
|
||||
{
|
||||
kv::Value {
|
||||
inner: Inner::Debug(unsafe { Erased::new_unchecked::<T>(value) }),
|
||||
cast::try_from_primitive(value).unwrap_or(kv::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.
|
||||
pub fn from_display<T>(value: &'v T) -> Self
|
||||
where
|
||||
T: fmt::Display + 'static,
|
||||
T: fmt::Display,
|
||||
{
|
||||
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)]
|
||||
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")
|
||||
);
|
||||
|
@ -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<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> {
|
||||
#[inline]
|
||||
fn from(v: u64) -> Self {
|
||||
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> {
|
||||
#[inline]
|
||||
fn from(v: i64) -> Self {
|
||||
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> {
|
||||
#[inline]
|
||||
fn from(v: f64) -> Self {
|
||||
Primitive::Float(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> From<bool> for Primitive<'v> {
|
||||
#[inline]
|
||||
fn from(v: bool) -> Self {
|
||||
Primitive::Bool(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> From<char> 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<fmt::Arguments<'v>> 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<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)
|
||||
}
|
||||
}
|
||||
|
@ -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<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
|
||||
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 {
|
||||
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(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")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<T>(value: &'v T) -> Self
|
||||
where
|
||||
T: ToValue,
|
||||
{
|
||||
value.to_value()
|
||||
}
|
||||
|
||||
/// Get a value from an internal primitive.
|
||||
fn from_primitive<T>(value: T) -> Self
|
||||
where
|
||||
|
Loading…
Reference in New Issue
Block a user