Support coercing structured Values into primitive types #379
No reviewers
Labels
No labels
bug
duplicate
easy
enhancement
help wanted
invalid
question
rfc
structured-logging
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-rust-lang/log!379
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/value-coercion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
For #328
This PR adds methods to
kv::Valueso you can attempt to coerce it into primitive types:I went with the
to_prefix instead of theas_prefix thatserde_json::Valuehas, because these aren't necessarily trivial conversions. The above example is, but something likeValue::from_fill(v)may end up needing to call arbitrary user code to produce the value.Doing this means our
kvsupport is enough that we could theoretically replace the existingRecordtype with something like this instead:We don't want to do that, but I think it's a good feature watermark to aim for so folks writing log frameworks can use the key values to store their own arbitrary data.
@ -53,3 +53,3 @@serde = { version = "1.0", optional = true, default-features = false }sval = { version = "0.4.2", optional = true, default-features = false }sval = { version = "0.5", optional = true, default-features = false }This minor bump is fine because this is all unstable.
I've refactored the module layout of
kv::valuea bit so it should be a bit easier to follow.r? @yoshuawuyts
I don't love the
get_prefix here. To me it sounds more like a lookup in some map structure than a type conversion. Even though there are costs associated usingas_orto_(e.g.i8::to_string) feel like better fits. This is also motivated because there is no precedent forget_in the stdlib. But I get why it exists, and there's no perfect perfect replacement either.TryInto?
This also got me thinking: how viable would it be to replace these inherent methods with
TryIntotrait impls?serde_jsonpredatesTryInto, so it makes sense it wouldn't have it. By usingNoneErrorthe behavior forOptionwould essentially be the same.I'm unsure how good of an idea this is, but this could be implemented with
TryInto<T> for &Valueso the conversion would never consume the original value (playground). From a look at the existing impls this should be able to cover the cases that have been laid out so far.Though admittedly I don't love this either, as usage could be a bit hard and it relies on
NoneErrorfrom nightly (there's ways around it, but argh).Left a note with some ideas that popped up while reading this; but that shouldn't be a blocker for merging this. This seems useful overall.
Thanks for the feedback @yoshuawuyts!
Yeh, the
getnaming does seem a bit iffy, and calling these coercions is also a bit inaccurate, since they’re also doing explicit casts. Perhaps we could do something like yourTryFromidea, but usingFromandOptioninstead?What do you think? We should be careful about implementing foreign traits for foreign types, but it could be a workable approach that also gives us a bunch of useful
From/Intoimplementations.I've refactored the
get_*methods into a more open cast API:which allows you to call:
or:
The
Into<Option<T>>bound seems a little strange, we could also consider usingTryInto<T, Error = Self>.So why
castand notdowncast? TheValue::castmethod is similar toBox::downcast, but has an important difference: it will attempt to cast the value to the target type, not ensure the type exactly matches.I think this is much more useful for logging cases where you aren't necessarily in control of what concrete values you're given, but need a way to check if it looks something like what you're expecting (attempting to cast a timestamp to a
u64for instance).So we can think of
Value::castas a more generalBox::downcast.So I followed the
Value::cast<T>thread and ran into a bit of a design problem with user-definedTryFromimplementations. Say we want to support attempting to downcast theValuetoTdirectly, that logic would either have to live in theValue::castmethod, and leaveTryFromimplementations behaving inconsistently, or it would have to depend on allTryFromimplementations calling the right incantations to get aTfrom theValue.So I've gone back to using inherent methods, with a
to_naming prefix. I've also added some proper downcasting support internally, which means we can do:which makes the casting API more reliable when you want to try get a specific kind of value out.
I think we should definitely revisit the open trait approach in the future though, and it's something that could pair nicely with the discoverable
to_methods.Thanks for taking the time to review this and providing feedback @yoshuawuyts!
@KodrAus oh I love that API! I def think you made the right call here; glad it ended up the way it did!