# Serde: the serialization framework Rust crates are built on

> Serde defines the Serialize and Deserialize traits and lets data formats live in separate crates. Here is how the derive macro, the serde_core split and the 1.0 release cadence affect anyone deciding whether to depend on it.

**serde-rs/serde** — Serialization framework for Rust

- Repository: https://github.com/serde-rs/serde
- Website: https://serde.rs/
- Stars: 10,837 · Forks: 946
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/serde-rs-serde

## What Serde solves, and who actually needs it

Rust's type system is strict about representation. A struct has fields in a fixed order, an enum has exactly the variants you declared, and there is no runtime reflection to walk either one. That is good for correctness and awkward for the moment you need to write a struct to a file or send it over a socket. Without a shared abstraction, every format would need its own hand-written conversion code for every type, and every type would need conversion code for every format.

Serde removes that multiplication. It defines two traits, Serialize and Deserialize, plus a data model that sits between your types and any concrete format. A type implements the traits once. A format crate implements the data model once. The two meet without knowing about each other. The README describes the project as "a framework for serializing and deserializing Rust data structures efficiently and generically," which is a fair summary of the contract.

The audience is Rust developers whose data leaves the program: HTTP handlers returning JSON, config loaders reading TOML or YAML, message consumers reading a binary payload, CLI tools writing state to disk. If your data never leaves memory, Serde adds compile time and dependency weight for no benefit.

## The trait boundary, the derive macro and the serde_core split

The repository is a Cargo workspace with five members: serde, serde_core, serde_derive, serde_derive_internals and test_suite. That layout tells you where the work happens. serde_derive is a procedural macro crate that reads your struct and enum definitions at compile time and emits trait implementations. serde_derive_internals holds the shared parsing logic that the macro uses to interpret attributes. test_suite is where the generated code is exercised against real types.

The serde_core member is the part worth understanding if you care about dependency graphs. The core trait definitions live in their own crate, which lets the derive macro depend on the trait definitions without pulling in the full facade. For a library author, this matters: a crate that only needs to implement Serialize can depend on the narrower piece rather than the whole thing.

At the data-model level, serialization walks your value and calls methods on a Serializer supplied by the format crate; deserialization runs the other direction through a Deserializer and a Visitor. The format crate decides whether that becomes a JSON string, a YAML document or a compact binary buffer. Your type never names the format. That is the whole design, and it is why the same #[derive(Serialize, Deserialize)] works across every format crate in the ecosystem.

## Installing Serde and getting a first struct through JSON

Serde is distributed through crates.io, so installation means adding dependencies to Cargo.toml. The README's example lists two crates: serde itself with the derive feature enabled, and a format crate. The comment in the README is explicit that the core APIs are always required and that the derive feature is only needed when you use #[derive(Serialize, Deserialize)].

```toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```

With those in place, annotate a struct and round-trip it. The README uses a two-field Point struct and prints both directions.

```rust
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 1, y: 2 };
    let serialized = serde_json::to_string(&point).unwrap();
    println!("serialized = {}", serialized);
}
```

Running that prints serialized = {"x":1,"y":2}, matching the output shown in the README. The reverse direction uses serde_json::from_str with a type annotation, and the README's example prints deserialized = Point { x: 1, y: 2 }. If the derive step fails, the error comes from serde_derive and usually points at a field type that does not implement the trait, or at an attribute the macro does not recognise.

## Where Serde stops: no schema, no versioning, no format guarantees

Serde is a translation layer, not a protocol. It has no concept of a schema, no field numbering, no compatibility rules and no way to tell you that a struct changed shape between two versions of your program. If you rename a field, old payloads stop deserializing unless you add an attribute that maps the old name. If you add a required field, every existing payload becomes invalid. Those decisions are entirely yours, and the framework will not warn you.

The second limit is that Serde is only as good as the format crate behind it. The core crate does not parse anything. If a format crate is unmaintained or has a bug in its Deserializer, Serde cannot compensate. Choosing Serde is really choosing Serde plus at least one format crate, and the second choice deserves as much attention as the first.

The third limit is compile time and binary size. Procedural macros run during compilation, and derive-heavy crates pay for it. Teams with large workspaces sometimes notice build times creeping up as the number of derived types grows. That cost is real and it is not something the README addresses.

Finally, Serde is the wrong tool when you need schema-driven interoperability with other languages. If a Go service and a Rust service must agree on a wire contract that evolves independently, a schema-first system will serve you better than a set of Rust attributes.

## Serde compared with schema-first alternatives like Protobuf

The most common comparison is Serde against Protobuf. They solve overlapping problems from opposite directions. Protobuf starts from a .proto schema file that is language-neutral, compiles it into generated types for each target language, and assigns numeric tags to every field so that adding or removing fields stays compatible across versions. Serde starts from your Rust types, requires no separate schema file, and derives the conversion code directly.

The practical difference shows up in multi-language systems. With Protobuf, the schema is the source of truth and the Rust struct is generated. With Serde, the Rust struct is the source of truth and the format is whatever crate you picked. Protobuf gives you field numbering and forward compatibility rules; Serde gives you less ceremony and no code generation step. Neither is universally better, but if your contract must be reviewed by teams that do not write Rust, the schema file is doing work that Serde cannot.

A second comparison is serde_json versus Serde itself. They are not alternatives. serde_json is a format crate that implements the Serde data model for JSON. The README's own example depends on both, and the comment in the Cargo.toml snippet notes that each data format lives in its own crate. Asking whether to use Serde or serde_json is like asking whether to use an interface or one implementation of it.

## Version cadence, the 1.0 promise and dual licensing

Serde has been on the 1.0 line for years, and the release history shows the pattern: v1.0.227 and v1.0.228 arrived two days apart in September 2025, and v1.0.229 followed in July 2026. The last push to the repository was on 2026-08-25. Patch releases at that interval are the norm for a library this widely depended upon; the maintainers are conservative about breaking changes because the cost of a break propagates through the entire ecosystem.

The README states minimum supported Rust versions of 1.56 for serde and 1.71 for serde_derive, presented as badges linking to the corresponding Rust release announcements. That split matters: if you enable the derive feature, your compiler floor is the higher number. Projects pinned to older toolchains can still use the core traits without the derive feature.

On licensing, the README states the project is dual licensed under Apache License 2.0 and the MIT license, at your option. The repository carries LICENSE-APACHE and LICENSE-MIT at the top level. The README also notes that contributions intentionally submitted for inclusion are dual licensed under the same terms unless you state otherwise. That is a permissive arrangement typical of Rust ecosystem crates, but it is a statement about the project's own licensing, not advice about how the terms interact with your product. If your organisation has rules about which of the two you elect, that is a question for your own review process.

## Conclusion

Adopt Serde if you are writing Rust that crosses a process, file or network boundary and you want one set of type annotations to serve JSON, YAML or a binary format. Do not adopt it if you need a stable wire format with schema evolution guarantees, since Serde itself defines no schema and no versioning rules. Before committing, check that your toolchain meets the minimums the README states for the derive feature (Rust 1.71) and confirm that your chosen format crate is still receiving releases.

## FAQ

### What does serde mean?

The name is a contraction of serializing and deserializing, which the README spells out in its opening line describing Serde as a framework for serializing and deserializing Rust data structures.

### How do you install serde in a Rust project?

Add serde to your dependencies in Cargo.toml with the derive feature enabled if you plan to use #[derive(Serialize, Deserialize)], plus a separate crate for whichever data format you need. The README's example uses serde = { version = "1.0", features = ["derive"] } alongside serde_json = "1.0".

### How do you use serde to serialize and deserialize a struct?

Derive Serialize and Deserialize on the type, then call a function from a format crate such as serde_json::to_string to convert it and serde_json::from_str with a type annotation to convert it back. The README's Point example prints {"x":1,"y":2} in one direction and the reconstructed struct in the other.

### What is the difference between serde and serde_json?

Serde supplies the Serialize and Deserialize traits and the shared data model; serde_json is one crate that implements that model for the JSON format. The README notes that each data format lives in its own crate, so serde_json is a companion rather than a replacement.

### What is serde in Rust used for?

It converts Rust data structures to and from external representations without tying your types to a specific format. The README describes it as a framework for serializing and deserializing Rust data structures efficiently and generically.

### How does serde compare with Protobuf?

Serde derives conversion code from your existing Rust types and leaves format choice to a separate crate, while Protobuf works from a language-neutral schema file with numbered fields. Serde has no schema or field-numbering concept, so compatibility across versions is something you manage yourself.

## Sources

- [License: Apache-2.0](https://github.com/serde-rs/serde/blob/master/LICENSE)
- [Project website](https://serde.rs/)
- [README](https://github.com/serde-rs/serde/blob/master/README.md)
- [Releases](https://github.com/serde-rs/serde/releases)
- [serde-rs/serde on GitHub](https://github.com/serde-rs/serde)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/serde-rs-serde
