Skip to content

Add as_borrow() generic method for Value #153

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from

Conversation

Kixunil
Copy link

@Kixunil Kixunil commented Sep 16, 2016

This change adds as_borrow() generic method for Value. It allows users to use e.g. some_fn(try!(value.as_borrow())) instead of some_fn(try!(value.as_string().ok_or(SomeCustomError)))

Ability to borrow mutably may be even more interesting.

It also defines nice error type for situations when types don't match as well as representation of value type. Those things create dependency chain.

Copy link
Member

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows users to use e.g. some_fn(try!(value.as_borrow())) instead of some_fn(try!(value.as_string().ok_or(SomeCustomError)))

I'm not sure that is sufficient motivation. Going overly generic with the as_borrow way sounds like it'll just make for unreadable error messages. Couldn't we just breaking-change the existing as_* methods to yield a Result<T, ValueType>?

/// Just like `std::borrow::Borrow<Value>` but borrowing may fail.
pub trait TryBorrow<'a>: 'a + ::std::marker::Sized {
/// Returns reference to internal data if type matches.
fn try_borrow(value: &'a Value) -> Result<Self, TypeError>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this trait could include the try_borrow_mut method

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is to mimic Borrow and BorrowMut. I'm not even sure whether it would be possible to have them together in one trait, since one is defined for &'a T and the other for &'a mut T...

However, I'm open to put them together, if it's possible and advantageous.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I forgot about that

}
}

impl<'a> TryBorrow<'a> for &'a bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all these impls should be abstracted away into a macro

Copy link
Author

@Kixunil Kixunil Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about it too lately. I'm not sure how to define the macro to be readable enough. I was thinking about something like this:

impl_try_borrows!( Bool => bool, I64 => i64, /* etc. */ );

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me

@@ -473,6 +559,99 @@ impl de::Deserialize for Value {
}
}

/// Represents type of a JSON value
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum ValueType {
Copy link
Member

@oli-obk oli-obk Sep 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type sounds a lot like std::mem::discriminant_value, so we could just wait until that is stable (3 Months probably)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. The library is still unstable, so we can wait. I hope time is not concern to anyone else.

I think I'm going to leave this as is for now until discriminant is stabilized. However, I'm not sure whether discriminant will allow to do exactly what I mean. I will check that out.

@Kixunil
Copy link
Author

Kixunil commented Sep 20, 2016

The motivation is mostly to let inference figure out the type. It would allow users to assign the resulting borrow into struct or passing it to function without thinking about types. Also, if they later decide to change the type, it will adapt automatically.

I'm not sure whether this will lead to unreadable messages. Do you have an example of such message?

I also noticed issue #126 (see also #127). This PR solves the naming problem quite elegantly. Instead of specifying the name in function (and deciding which is the best), it allows user to specify the exact type. At least for .as_* (Personally, I'd drop all .as_* fns and replace it with this one. Possibly also with moving counterparts.) This is especially useful in case of &str/&String or even more in mutable cases.

I believe something similar could be implemented for .is_* case but that could be also viewed as complicated. That's why I didn't code it yet. I'm willing to write the change if it's desired.

Finally, please note that I intentionally used custom error type, so it can be displayed to user in very human friendly way. ("type mismatch: expected type: X found type: Y")

I hope I made myself clear enough. I'm looking forward for your thoughts!

@dtolnay
Copy link
Member

dtolnay commented Dec 24, 2016

@oli-obk what is the status of this PR? Is it waiting for stable std::mem::discriminant? Why is that better than ValueType? Have you found any unreadable error messages from this?

@oli-obk
Copy link
Member

oli-obk commented Dec 24, 2016

Why is that better than ValueType

Just less code for the same effect

Have you found any unreadable error messages from this?

Imagine calling the method on a &str target. There's no impl for it and no way for the user to write one

Will review more later

@dtolnay
Copy link
Member

dtolnay commented Feb 26, 2017

Reading through this again, I don't think this would be a beneficial change. It seems like the three main motivations for this PR are:

  • Genericity, being able to write a function that takes a type parameter controlling what type to borrow as. Is this a real use case?
  • Flexibility, being able to borrow as &str as well as &String. Can you give any examples of real code that is not well served by the existing API? I think all of the examples in the doc comments would be as clear or clearer using the existing methods.
  • Error messages, like "expected type: Map found type: String". I think this PR would actually be detrimental to error messages. If my app crashes with that message, I have no idea what to do. In Serde 0.9 we have tried to hold a somewhat higher bar for error messages than in previous releases, and sometimes that means forcing people to think about what error case they are handling and how. An actionable message requires more context than what would be available in Value::as_borrow, for example "book.json: expected configuration for `edit-link` plugin to be a map"

@dtolnay dtolnay closed this Feb 26, 2017
@Kixunil
Copy link
Author

Kixunil commented Mar 6, 2017

This PR was posted a long time ago and serde changed quite a lot since then. I'd have to look at it again and re-validate. I'm fine with closing now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants