News for hackers. Stuff that matters.
Re:The Serde Rust Framework by UK-Al05 on Thursday October 14, 2021 @05:48PM (#28870618)
I think in most ecosystems it's easier to use a prebuilt serializer/deserializer than your own format.
Re:The Serde Rust Framework by Jweb_Guru on Thursday October 14, 2021 @07:03PM (#28871390)
Nah, not like it is in Rust. For the vast majority of projects, serde makes parsing / serializing something they don't even have to think about, without having to sacrifice type safety to get there. Until you experience that in a large project with a lot of moving parts and developers, it's hard to appreciate just how many developer cycles you were wasting before on stuff that can be totally automated. It's format-agnostic, it's really fast (not the absolute fastest, but fast enough for most situations), it's insanely customizable for even the weirdest use cases, it's absolutely everywhere, it incorporates proper error handling, and adding it to your own types is trivial.

Is there any actual reason something like serde couldn't exist in other languages? None that I know of, but it doesn't, at least not when you consider just how pervasive serde is in the ecosystem (almost every library that stores things you want to serialize will have it). I really hope every language can standardize on something similar; it just makes development so much nicer and faster when you don't have to worry about this stuff.

Re:The Serde Rust Framework by throwaway894345 on Thursday October 14, 2021 @07:57PM (#28871948)
> For the vast majority of projects, serde makes parsing / serializing something they don't even have to think about, without having to sacrifice type safety to get there.

This has not been remotely my experience. Deserializing has a lot of implications for ownership and you get into complicated deserializer trait specifications pretty quickly.

In particular, there doesn’t seem to be a way to deserialize a type that has a reference because you don’t seem to be able to tell serde who should own that memory. Maybe I’m wrong, but I’ve walked through this multiple times with experienced Rust users and no one could get it working.

Serde is impressive, I’m sure, but far from “don’t even have to think about it”.

Re:The Serde Rust Framework by roca on Thursday October 14, 2021 @08:25PM (#28872211)
We almost always deserialize to owned data, partly because our deserialization source is almost always compressed so the data has to be copied. There are a few cases where we want to serialize with a reference and deserialize to owned, but that's no big deal using custom "with" functions.

We have hit one serde footgun, which is that skip-serializing-if corrupts your data with formats like bincode :-(. https://github.com/serde-rs/serde/issues/1732 really wish that could be fixed.

Re:The Serde Rust Framework by Const-me on Thursday October 14, 2021 @10:20PM (#28873343)
> Is there any actual reason something like serde couldn't exist in other languages?

C# standard library has DataContractSerializer for decades. That thing reads/write text XML by default, but with minor tweaks can do binary XML or JSON.

I like their binary XML format the most. Very fast because doesn’t waste time printing or parsing numbers or Base64 bytes. Also, pre-shared XML dictionary, and session-accumulating dynamic dictionary, makes the serialized representation very compact, sometimes an order of magnitude better than text/xml.

Re:The Serde Rust Framework by fiddlerwoaroof on Thursday October 14, 2021 @08:45PM (#28872388)
These sorts of things are really convenient, but the idea of tightly binding your type definitions to serialization formats always makes be a little nervous: I haven’t used Rust enough in practice to have an opinion here, but I’m wary of a solution in this space that makes it too easy to ignore the forward/backwards compatibility of your external format.
Re:The Serde Rust Framework by Jweb_Guru on Thursday October 14, 2021 @08:49PM (#28872428)
Yeah, for sure that is something to be cautious of. For cases where I expect backwards compatibility to be a concern, I will usually make the top-level type of the message or format an enum with each variant being a version (initially with just a single version, V1 or V0). That way, if we need to make backwards-incompatible format changes, we can create a new variant, and potentially even automatically migrate between formats (I've done this a few times). Then all we have to do is call a function to migrate to the latest format, and we can use whatever that is for all the actual program code (generally it will be a type alias for the version structure of the same name). Of course, if this gets too complex or you start to have non-treelike relationships between values, you still want to fall back on a more traditional database schema, but this kind of versioning can get you a long way.

That said, it's incredibly refreshing to be able to focus on these semantic and migration concerns without having to worry about how to deal with parsing them later. serde doesn't solve enforcing backwards compatibility for you, but it sure makes doing the right thing a lot easier. You never have to worry about some code accidentally forgetting to check the version field, people carelessly mixing and matching messages with different versions, or fields that "should" never be set but are there for backwards compatibility. That's worth a lot to me!

Re:The Serde Rust Framework by UK-Al05 on Thursday October 14, 2021 @09:12PM (#28872647)
I rarely have to think about deserialisation in c# or java either...
Re:The Serde Rust Framework by Jweb_Guru on Thursday October 14, 2021 @10:58PM (#28873658)
I wrote a lot of Java back in the day. I elaborated on why I disagree in the other comment. I do not know if the situation in C# is similar.
Re:The Serde Rust Framework by Yacoby on Friday October 15, 2021 @12:54AM (#28874443)
For context, the example from the Serde wiki written in Java using Jackson

  public record Point(int x, int y) {}

  // [...]

  var objectMapper = new ObjectMapper();
  var jsonString = objectMapper.writeValueAsString(new Point(2,1));
It isn't obvious to me what advantage Serde offers that you couldn't get with Jackson or other similar libraries in Java (although I get that Java and Rust are different languages, and that there may not be something this ergonomic in the likes of C++)
Re:The Serde Rust Framework by Jweb_Guru on Friday October 15, 2021 @01:46AM (#28874712)
For that example there's no advantage. But it's really, really easy to find examples where there are (the simplest example is, anything with pointers with @NonNull annotations, or anything which is performance-sensitive and contains other objects).
Re:The Serde Rust Framework by pjmlp on Friday October 15, 2021 @01:47AM (#28874720)
That has been the same in Java and .NET world for 20 years now, just plug the desired serializer from the standard library and be done with it.
Re:The Serde Rust Framework by Jweb_Guru on Friday October 15, 2021 @02:03AM (#28874804)
It absolutely does not provide the same guarantees, and I've covered why in several posts now. Do you believe that people who disagree with you have never used any language other than Rust before? Having to deal with data structure invariants getting violated due to reflection is a huge headache and makes the parsers way less useful than they would be otherwise.
Re:The Serde Rust Framework by pjmlp on Friday October 15, 2021 @02:50AM (#28875055)
Yes, because those same people seem to be unaware reflection is not the only way to use such APIs, revealing a superfical knowledge of how those ecosystems have been working for the last 25 years.
Re:The Serde Rust Framework by spoiler on Friday October 15, 2021 @03:22AM (#28875257)
How is it implemented in Java or C# if not via runtime reflection? I've always assumed that's the way its done...
Re:The Serde Rust Framework by brabel on Friday October 15, 2021 @07:20AM (#28876582)
Java has annotation processors which can perform code generation, just like Serde does.

Manifold [1] an example framework that relies heavily on that, achieving similar things like Serde.

Java developers don't like to use these things too much though, unlike Rust developers who love their `#derive`, so you're mostly right that reflection-based serialization is still more common, but that's by choice.

Dependency Injection is in a similar situation: frameworks like Micronaut [2] can do it without reflection, and things like Google Dagger [3] have existed for several years that do the same thing... but still, most Spring Boot-based projects I know of use reflection-based DI as well... it's fast and the security issues have been largely mitigated nowadays.

[1] http://manifold.systems/

[2] https://docs.micronaut.io/latest/guide/

[3] https://rskupnik.github.io/dependency-injection-in-pet-proje...

Re:The Serde Rust Framework by spoiler on Friday October 15, 2021 @12:39PM (#28879757)
Thank you for the detailed write up! I love finding an out about cool language feature/tools
Re:The Serde Rust Framework by andrewaylett on Friday October 15, 2021 @05:49AM (#28876007)
I'd always recommend folk use constructor-based deserialisation, rather than getting the deserialiser to create empty objects and poke at fields.

That's still (at least naively) using reflection, but it's a lot safer.

In practice, using reflection for every object being deserialised is far too slow and Jackson at least generates code at runtime rather than repeatedly reflecting. That's magic, but it's nice magic because it's not breaking the language: one can see that it's possible to implement it in pure Java.

Re:The Serde Rust Framework by Const-me on Friday October 15, 2021 @07:41AM (#28876700)
I don't program Java, but I have implemented and used similar things for C#.

Reflection is indeed used there, but not to serialize. It's only used once per type, to generate code.

C# has multiple ways to generate code in runtime. One is Reflection.Emit, allows to manually generate bytecode instructions + metadata. For instance, one can build new types in runtime. A typical pattern is implementing manually-written abstract class or interface with generated types: this way the manually-written code can call into runtime generated one.

Another method is System.Linq.Expressions. This one allows to generate code (no new types though, just functions) from expression trees, and provides API to build and transform these expression trees.

Regardless on the method, the generated code is no different from manually written code. JIT compiler can even inline things across, when generated code calls manually written one, or vice versa.

Re:The Serde Rust Framework by spoiler on Friday October 15, 2021 @12:47PM (#28879885)
Ah the bytecode generation via Reflection.Emit seems like a really powerful feature. Does it feel "brittle" to use, or is it expressive enough to aid writing more robust code (to a degree, I realise it's not possible to aliminate every problem)?

There's a weird (not in a bad way) elegance here that reminds me of lisp.

Re:The Serde Rust Framework by Const-me on Friday October 15, 2021 @05:48PM (#28883519)
> Does it feel "brittle" to use

Yes and no.

No because when you try to do unsupported things like calling a method on an object which doesn’t support one, you gonna get an appropriate runtime exception.

Yes because if you fail lower-level things like local parameter allocation, you gonna get an appropriate runtime exception but that one is (1) too late, I’d prefer such things to be detected when you emit the code, not when trying to use the generated code (2) Lacks the context.

Overall, when I can I’m using that higher-level System.Linq.Expressions for runtime codegen. Things are much nicer at that level. I only using the low-level thing when I need to emit new types, like there: https://github.com/Const-me/ComLightInterop/blob/master/ComL...

Re:The Serde Rust Framework by pjmlp on Friday October 15, 2021 @06:22AM (#28876195)
You can use stuff like VS T4 templates or compiler plugins via annotation processors.

So at compile time, just like serde works, you create your type safe serializer.

Re:The Serde Rust Framework by UK-Al05 on Friday October 15, 2021 @05:36AM (#28875925)
Why would reflection break invariants? Most deserializers can pass through data into the constructor via reflection as well, where you can protect against invalid objects.
Re:The Serde Rust Framework by dathinab on Friday October 15, 2021 @06:51AM (#28876380)
Yay, except that at least Java does so by using reflections and has lead to more then just a few massive security vulnerabilities.
Re:The Serde Rust Framework by dathinab on Friday October 15, 2021 @07:44AM (#28876716)
Just to be clear, there are Java libraries which do thinks better in a less problematic way, just the facilities shipped in the standard library are IMHO inadequate.
Re:The Serde Rust Framework by brundolf on Thursday October 14, 2021 @11:47PM (#28874016)
Macros are definitely key. I wonder if ownership helps too? Circular references are always weird when serializing
Re:The Serde Rust Framework by pornel on Thursday October 14, 2021 @06:13PM (#28870897)
The trick with Serde is that it decodes straight into a native Rust struct. You get most of validation for free (it also nicely takes advantage of Rust enums with data), and struct access is maximally fast.

A generic JSON decoder would give you a dynamic structure that can contain anything, and then you'd have to pick it apart.

Re:The Serde Rust Framework by UK-Al05 on Thursday October 14, 2021 @06:26PM (#28871024)
Most deserializers in typed languages allow you to deserialize straight into structs or typed objects...

Serde just doesn't use reflection to do it.

Re:The Serde Rust Framework by Jweb_Guru on Thursday October 14, 2021 @07:15PM (#28871506)
APIs like GSON and Jackson have a lot more footguns, because (1) they attempt to (de)serialize types whether or not they were ever intended to be (de)serialized, (2) they are missing information about important constraints due to losing information at runtime (the excuse usually used here is that "they don't do validation" when in reality they are violating expectations of package modularity and encapsulation in a pretty blunt way). For example, they will happily ignore non-null constraints on your fields. In practice, this is a major annoyance and ends up making you very paranoid about (de)serializing any structure you don't have full control over.

Performance of a lot of the reflection-based parsing APIs also leaves something to be desired, which means that projects with more stringent performance requirements often have to resort to stuff like Avro. This still happens with serde, but much more rarely--besides having more compile-time information at its disposal and needing to allocate less, it also provides relatively straightforward hooks to achieve things like zero-copy deserialization for strings where the whole buffer is available at once.

Re:The Serde Rust Framework by dcow on Friday October 15, 2021 @02:08AM (#28874825)
I’m pretty sure the non-null issue is fixable with a single config option, at least in GSON IIRC.
Re:The Serde Rust Framework by Jweb_Guru on Friday October 15, 2021 @02:10AM (#28874837)
I don't believe it's fixable at all if you're using someone's third-party library that wasn't designed for GSON (unless something has changed a lot since I last used it). The annotation has to be stuck on the original field, AFAIK. And similar stuff apply to other invariants on the type. I believe these kinds of issues are somewhat fundamental when you decide you're going to start (de)serializing structures that never thought about whether or how they'd like that to happen.
Re:The Serde Rust Framework by unnah on Friday October 15, 2021 @09:24AM (#28877424)
In Jackson, you can use mixin classes to define annotations for 3rd party libraries.
Re:The Serde Rust Framework by Taywee on Thursday October 14, 2021 @06:43PM (#28871190)
C++ isn't super easy about it, though. The best C++ json library (in my opinion), nlohmann's json, still requires you to define to_json and from_json for your types to use them. It's not too bad as a one-off, but when you have dozens of types, it gets really tedious compared to Serde, and managing std::variant with it is way more annoying than Serde with Rust enums.
Re:The Serde Rust Framework by berkut on Thursday October 14, 2021 @07:30PM (#28871641)
However, if you just want a very minimal subset of a JSON response (i.e. a 'status' string field out of say 20 other values in a map), is it not better to just look for one field, as opposed to have to create an entire struct in Rust to represent the full response?

How does Serde work with optional extra fields? I know you can use Option<>, but that implies you know they exist - what happens if fields get added in the future silently? I guess the schema changes then, which isn't good, but that might happen?

Re:The Serde Rust Framework by nemothekid on Thursday October 14, 2021 @07:45PM (#28871823)
An "entire" Rust struct containing a single string field is just a struct with a single field. You don't have to specify the fields you don't need and it's unlikely your handrolled parser would be faster than serde (unless you are using memchr or something, then in that case you aren't parsing JSON).
Re:The Serde Rust Framework by rokob on Thursday October 14, 2021 @07:42PM (#28871788)
You can add a catch all field that captures those in a map. You can also choose to ignore things you don’t explicitly mention. It is up to you how strict you want the parsing to be.

So you can define a struct with one status field and the right annotations and you will get exactly what you describe without having to write the code and it will still be almost as fast as doing that parsing yourself.

Re:The Serde Rust Framework by codys on Thursday October 14, 2021 @08:31PM (#28872261)
If you only want a single field in json, you define a struct with just that single field. Serde does have the ability to return an error about unknown fields, but it is not enabled by default.

This also informs your second question. If new optional fields in json are added, unless you tell serde to complain about them, it won't, and you can add the new Option at your leisure.

Re:The Serde Rust Framework by kroltan on Thursday October 14, 2021 @07:45PM (#28871817)
As far as I understand, unmapped fields are simply ignored, so you can write your code like

    #[derive(Deserialize)]
    struct Response { status: u64 }
Re:The Serde Rust Framework by masklinn on Friday October 15, 2021 @01:11AM (#28874538)
> However, if you just want a very minimal subset of a JSON response (i.e. a 'status' string field out of say 20 other values in a map), is it not better to just look for one field, as opposed to have to create an entire struct in Rust to represent the full response?

You can define a struct with just the status field.

> How does Serde work with optional extra fields? I know you can use Option<>, but that implies you know they exist - what happens if fields get added in the future silently? I guess the schema changes then, which isn't good, but that might happen?

Unknown fields are skipped by default.

Re:The Serde Rust Framework by roca on Thursday October 14, 2021 @06:01PM (#28870769)
Fair enough. I have not found that to be true in C/C++, which is my main point of comparison. For very simple cases it's generally easier to write a few lines to a text file, maybe with some whitespace separators in the lines, than to use an "real" format.

For JS and Python, JSON.stringify/parse and json.loads/dumps are a step up, but you still end up with an untyped mess with no schema validation, which makes them only halfway solutions to me. I'm a static typing guy at heart, sue me.

Re:The Serde Rust Framework by eptcyka on Thursday October 14, 2021 @06:19PM (#28870961)
Yea, but with serde, you don't get an untyped mess. You either get the type you expected or you get an error.
Re:The Serde Rust Framework by vips7L on Thursday October 14, 2021 @07:08PM (#28871437)
Same is true in any typed language.
Re:The Serde Rust Framework by spoiler on Thursday October 14, 2021 @08:39PM (#28872337)
I don't think that's true. Or at least we have different ideas about what's being discussed...

Let's take JSON or YAML for example:

Rust's closest siblings C and C++ are typed, and you get a pretty much untyped messes when working with serialisation or deserialisation. You have to manually inspect every node or write manual ”NodeType" to struct conversation. They allow unwrapping to primitives at best (eg via template specialisation).

In Haskell it's a bit more awkward, but you can kinda pull something similar off in terms of ergonomics. I've not worked with JSON in Haskell that much that I need to look for it, but I don't remember there being something as ergonomic as serde.

Typescript doesn't support this type of validation either out of the box (there might be tools that add validation).

In Elixir there's the Poison library, or it could be done with Kernel.struct/2 (not 100% sure this will work though).

Of the "mainstream" languages Go is pretty much the only one that has as good ergonomics as Rust in that it supports it pretty much natively (via struct tags)

---

This list excludes codegen tools which can generate (de)serialisers from an external schema (a la capnp, grpc, jsonschema, etc).

Re:The Serde Rust Framework by dwohnitmok on Thursday October 14, 2021 @11:27PM (#28873873)
> In Haskell it's a bit more awkward, but you can kinda pull something similar off in terms of ergonomics. I've not worked with JSON in Haskell that much that I need to look for it, but I don't remember there being something as ergonomic as serde.

I think Haskell's most popular JSON library aeson is basically the same thing and probably a source of inspiration for serde.

  data MyCustomType = ... deriving (Generic, ToJSON, FromJSON)
or if you want more control you can use deriving-aeson

  -- I prefer snake case instead
  data MyCustomType = ...
    deriving Generic
    deriving (FromJSON, ToJSON)
    via CustomJSON '[FieldLabelModifier CamelToSnake]
Re:The Serde Rust Framework by spoiler on Friday October 15, 2021 @03:04AM (#28875143)
Ah, I've heard about aeson, but not used it. Thanks for pointing it out!
Re:The Serde Rust Framework by rudedogg on Thursday October 14, 2021 @10:08PM (#28873246)
> Of the "mainstream" languages Go is pretty much the only one that has as good ergonomics as Rust in that it supports it pretty much natively (via struct tags)

Swift does this too, using the Codable Protocol. The compiler generates the protocol conformance code for you, if you say a type is `Codable`, and the properties the type contains are all Codable types (Int, Float, String, URL, etc. conform to it), you don't have to do anything.

https://developer.apple.com/documentation/foundation/archive...

Re:The Serde Rust Framework by spoiler on Friday October 15, 2021 @03:08AM (#28875166)
Ah, I don't use Apple devices, so I've never been able to play with Swift(1). So I didn't know Swift had it too; thank you for pointing it out!

(1): which is a shame, I remember when it came out that it seemed like a nice language

Re:The Serde Rust Framework by sli on Friday October 15, 2021 @05:11AM (#28875802)
Swift is available for Windows 10 and a few Linux distros (Ubuntu, Cent, and Amazon Linux 2) according to their download page.[0] If you're still interested, go give it a shot.

[0]: https://swift.org/download/

Re:The Serde Rust Framework by UK-Al05 on Thursday October 14, 2021 @09:23PM (#28872751)
Most typed languages it's like this

var account = JsonConvert.DeserializeObject<Account>(string/stream)

That's it. No inspection or looping through trees. You can often get it to create objects via constructor for validation.

It's been like this for 15 years...

Re:The Serde Rust Framework by vips7L on Thursday October 14, 2021 @09:31PM (#28872842)
Maybe we are having different ideas about what is being discussed, but in Java and C# (arguably the most mainstream languages) you have the same experience supported natively via annotations and attributes.
Re:The Serde Rust Framework by spoiler on Friday October 15, 2021 @03:18AM (#28875236)
Ah, I stand corrected then.

I've worked with the Microsoft tech stack very very little, so I've never bothered to learn C#.

I've completely forgot about Java; I haven't used it in ages. Now that you mentioned Java, I realised Kotlin has it too.

Re:The Serde Rust Framework by dcow on Friday October 15, 2021 @02:20AM (#28874890)
Swift also has native support. I am actually not a huge fan of the ergonomics of Go’s struct tags because of how cluttered they get in practice. It feels so unstructured like the rest of the language, but maybe that makes it perfect for Go.
Re:The Serde Rust Framework by dexwiz on Thursday October 14, 2021 @06:02PM (#28870779)
Yeah I don’t know why you would try to write your own textbased format. GSON and Jackson have been around for over a decade in Java, and equivalents in other languages. Unless you are in some perf critical application, a library is almost always better for this problem.
Re:The Serde Rust Framework by thedracle on Friday October 15, 2021 @12:11AM (#28874199)
At least in languages with similar native performance as Rust (C/C++), in my personal experience, serialization has often been difficult and manual, and most libraries have fallen short of my expectations, or had serious performance, security, or toolchain issues, to the point I usually hand-serialize myself.