How to unify around a struct that can be both TryFrom'ed and From'ed

I’m working on the unic-locale crate and am trying to improve the ergonomics of working with it.

Here’s my example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9beb559059dc314a4e1008072726b342

In particular, there’s a struct called LanguageIdentifier which is sort of a view into a parsed string, but no a simple slice since it also normalizes the casing of the string (“En-uS” -> “en-US”).

For that reason, my struct stores String rather than &str or Cow types.

Now, the common way to pass an identifier around, and a common scenario for an input is a string, which has to be parsed. I use TryFrom for that, allowing users to easily attempt to build a new LanguageIdentifier from a &str.

There’s also a struct called Locale which contains a LanguageIdentifier plus additional data, and this struct can be safely casted into LanguageIdentifier, so I use From on that.

My question is - how can I produce a function that can take a &str, a LanguageIdetifier, a Locale, or, in fact, anything that can be casted into LanguageIdentifier, negotiates it, and returns one of the input arguments?

For the sake of the example, I simplified it so that my example only takes two arguments, but in real code, those will be two vectors of the values (requested and available) which will negotiate down to a sorted subset of available.
So in a perfect world, I also wouldn’t mind taking references to some generic type that can be cased to LanguageIdentifier, and returning the sme referneces as well, but that may be even harder.

Can anyone suggest the way in Rust to write such ergonomical code?

Everything that implements From also implements Into (src), and everything that implements Into also implements TryFrom with Error = Infallible (src). So I think you should just be able to write your code for TryFrom and it will all work.

Furthermore, all T: TryFrom also implements TryInto (src), so that would be the most flexible.

1 Like

Here’s a modified version of your example that makes use of the blanket impls as described above:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2e4c1fa6fe281b85316bd4ef0d0c50ca

You’ll probably want to do something smarter than unwraping inside of negotiate_languages (such as providing a sane default) but hopefully that shows you the general idea