Open-source project
Lokathor/tinyvec avatar
Lokathor/tinyvec

tinyvec gives you vec-like types without a single line of unsafe Rust

Just, really the littlest Vec you could need. So smol.

754 stars67 forksRustApache-2.0

At a glance

What is it?
tinyvec is a Rust crate of array-backed and inline-or-heap vector types that forbid unsafe code outright, at the cost of requiring element types to implement Default. That constraint is the whole design, and it is also the reason the crate sits beside arrayvec and smallvec rather than replacing them.
Who is it for?
Adopt tinyvec when your element type does not implement Default, when you are writing no_std code, or when a hard guarantee against undefined behaviour matters more than the flexibility that unsafe implementations give you. Do not adopt it as a drop-in replacement for smallvec if your elements are non-Default types, because the requirement is enforced at the type level rather than worked around.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 38 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The crate's headline is the forbid attribute

The description of the crate on its own repository is a joke with a technical payload: just, really the littlest Vec you could need, so smol. The README then states the substantive claim, that tinyvec is a 100% safe crate of vec-like types, and makes the claim stronger than most crates bother to. It is not just safe at the public API boundary, it is fully safe for all internal code too, which the README backs with the attribute `#![forbid(unsafe_code)]`.

Forbid is stronger than deny in Rust. Deny can be overridden at a narrower scope, forbid cannot. The crate does not contain unsafe code, and it does not permit you to introduce any in a downstream build either.

That property is the product. The audience is Rust developers who are writing no_std code for embedded targets, who are working in a codebase with a policy against unsafe, or who simply do not want to reason about manual memory management for a container type. The crate metadata lists no_std as a keyword and puts the project in the no-std category, and the minimum supported Rust version badge says 1.47, which dates the crate to a conservative toolchain floor.

It is a library, not a framework. There is no runtime, no configuration and no build script to speak of. You add a dependency, choose a type, and the API is the standard vec-like surface.

Three types, one of which changes its own representation

The README lists the provided types, and the distinctions between them are the whole API surface.

ArrayVec is an array-backed vec-like data structure that panics on overflow. The storage is inline, in the struct itself, and there is no heap involved.

SliceVec is similar, but uses a `&mut [T]` as the data backing. It holds a slice someone else owns, which is how you use a fixed region without copying into it.

TinyVec, which exists only with the `alloc` feature, is an enum that is either an Inline(ArrayVec) or a Heap(Vec). The interesting part is the transition. If a TinyVec is Inline and would overflow its array, it automatically transitions to Heap and continues whatever it was doing. The caller does not have to check the length before pushing, and the value does not have to be rebuilt.

So there is a natural progression for a user. Start with ArrayVec when the size is known and small, move to SliceVec when the storage belongs to someone else, and reach for TinyVec when the size is not known in advance but the common case is small. All three share the vec-like API, so the migration is mostly a type change.

The cost of the safety guarantee is stated in one sentence in the README. To attain this 100% safe code status there is one compromise: the element type of the vecs must implement `Default`. Everything else about the design follows from that single restriction.

The Default bound is the price, and it is not a small one

Requiring `Default` on the element type sounds mild until you work out why it is needed. The safe implementations of an array-backed vector need a way to initialise the unused capacity in the array. Without unsafe code, the only general mechanism is to construct a `Default` value and write it into the spare slots, which means every element type in the vector has to be constructible that way.

The practical effect is that `tinyvec` will not hold `Option<NonZeroU32>` or any other type that has no meaningful default, and it will not hold a type whose default is expensive to construct, because that cost is paid for every element of the inline capacity rather than only for the elements you actually use.

This is exactly the trade the README describes when it compares itself with the alternatives. The arrayvec and smallvec crates use unsafe code, and the README says this mostly allows them to get rid of the `Default` limitation for elements that `tinyvec` imposes. So if your element type cannot implement `Default`, smallvec is the crate that will let you use it, and `tinyvec` is the crate that will refuse to compile.

That refusal is the feature. A compile error is a cheap, immediate, permanent answer to the question of whether your data can be represented, and it is the reason the crate can make the safety claim without qualification. A caller who hits the bound knows exactly what to change, and it is never the case that the vector silently misbehaves at run time because of an initialisation shortcut.

The feature flags decide what the crate can even name

The Cargo manifest is where the shape of the crate is clearest, and it starts from nothing:

toml
[features]
default = []

# Provide things that utilize the `alloc` crate, namely `TinyVec`.
alloc = []

# Provide things that require Rust's `std` module
std = ["alloc"]

Default features are empty, so a default dependency gives you the array-backed types and nothing that needs an allocator. The `alloc` feature adds TinyVec, and `std` is a superset that requires `alloc`.

There are also integration features for the serialization ecosystems. The serde feature is a thin switch:

toml
# Provides `Serialize` and `Deserialize` implementations
serde = ["dep:serde_core"]

The optional dependencies in the manifest go well beyond serde. There is arbitrary for derived Arbitrary implementations, borsh for Borsh serialization, generic-array, which implements the `Array` trait for the `GenericArray` struct, bin-proto for BitEncode and BitDecode, defmt for Format implementations, and schemars for JsonSchema. Every one of them is optional and every one is disabled by default, which is the point: the crate can be dropped into a firmware build without pulling a serialization stack along.

There is also a feature named grab_spare_slice, which the manifest describes as extra methods to let you grab the slice of memory after the active portion of an ArrayVec or SliceVec, and it is explicitly noted as not part of Vec. And the manifest keeps obsolete features such as rustc_1_40, with a comment saying they have to stay for semver reasons, which is how a crate keeps old Cargo.toml files compiling.

Why the alternatives exist and why you might still pick them

The README has a section titled `tinyvec` Alternatives? and it is unusually candid. It names arrayvec, a crate with array-backed structures, and smallvec, a crate where the array-backed data can be moved to the heap on overflow.

The stated difference is the use of unsafe code. The README says both of those crates are generally correct, but that there has been occasional bugs leading to UB, and that with tinyvec any uncaught bugs can't lead to UB because the crate is safe code all the way through. If you want that absolute level of assurance against UB, the README says to use tinyvec.

That is a real argument and it is also the whole argument. The comparison is not about speed, since the README makes no performance claim and the crates directory contains a benches folder and a compare_benchmarks.py script rather than published numbers you are expected to trust. It is about where the risk sits. With smallvec you are trusting a small, well tested amount of unsafe code. With tinyvec you are trusting that the compiler keeps the crate honest, because there is no unsafe to be wrong about.

The corollary is that you may be giving up capability. The `Default` bound rules out element types that have no default, and the absence of unsafe means there is no way for the crate to be more clever than the safe subset allows. If you need either, smallvec is the better fit and the safety argument does not apply to you.

Versioning, licences and the state of the repository

The manifest records version 1.13.3, edition 2018, and the licence as a choice of three:

toml
version = "1.13.3"
authors = ["Lokathor <[email protected]>"]
edition = "2018"
license = "Zlib OR Apache-2.0 OR MIT"

The repository carries the matching licence files, LICENSE-APACHE.md, LICENSE-MIT.md and LICENSE-ZLIB.md, so a downstream project can pick whichever of the three it prefers, which is the usual arrangement for a crate that wants to be usable in projects with different policies. The README badge, though, shows only the Zlib licence, which is a small inconsistency between the badge and the manifest. If the licence choice matters to you, read the manifest and the three files rather than the badge.

The repository is not archived, and the last push was on 2026-09-16. There are no GitHub releases, so the version history lives in the crate registry and in the changelog.md file at the root rather than in tags you can browse.

The rest of the tree tells you how the crate is tested. There is a benches directory, a fuzz directory, a tests directory, and a debug_metadata directory, alongside a src-backup directory that the manifest excludes from the published package along with the .github directory and the shell and Python scripts. A fuzzing setup in a crate whose entire value proposition is memory safety is a sensible thing to find, and it is a reminder that the crate is maintained with the same rigour as any other storage type.

Where tinyvec is the wrong choice

The first wrong case is a non-Default element type. This is not a limitation you can work around with a different feature flag, it is the central constraint of the design. If your elements are a non-Default type, smallvec will accept them and tinyvec will not, and the right answer is to use the crate that supports your data.

The second is a large element type in a large inline capacity. The Default bound means the spare capacity of an ArrayVec has to be filled with default values, so an inline vector of a type with an expensive default pays that cost at construction rather than only for the elements you push. For a vector of large structs, the inline approach can cost more than it saves.

The third is a need for the full Vec surface. The grab_spare_slice feature is explicitly noted as not part of Vec, and any other gap in the API means reaching for the standard library's Vec and paying for the heap. tinyvec is deliberately the littlest Vec you could need, and there are Vec features it does not carry.

The comparison to make is with simply using Vec. If your collection is always large, or always heap-bound anyway, an inline-capable type buys nothing and the enum in TinyVec adds a branch on every access. The crate earns its place in the specific case of a small collection that is nevertheless dynamic in size, where a fixed array is too rigid and a heap allocation is too heavy for the workload.

Editorial conclusion

Adopt tinyvec when your element type does not implement Default, when you are writing no_std code, or when a hard guarantee against undefined behaviour matters more than the flexibility that unsafe implementations give you. Do not adopt it as a drop-in replacement for smallvec if your elements are non-Default types, because the requirement is enforced at the type level rather than worked around. Before you pin it, read the changelog.md in the repository and check the published version, since the crate has no GitHub releases, and note that the Cargo manifest offers Zlib, Apache-2.0 or MIT while the README badge shows only the Zlib licence.

Frequently asked questions

What types does the tinyvec crate provide?

ArrayVec is an array-backed vec-like structure that panics on overflow, SliceVec is similar but backed by a `&mut [T]`, and TinyVec, available with the `alloc` feature, is an enum that is either an Inline ArrayVec or a Heap Vec and automatically transitions to the heap on overflow.

Why does tinyvec require element types to implement Default?

Because the crate contains no unsafe code, the spare capacity of an inline array has to be initialised some other way, and `Default` is the mechanism. The README calls this the one compromise needed to attain 100% safe code, and it is the reason arrayvec and smallvec can support element types that tinyvec cannot.

What licence is tinyvec under?

The Cargo manifest states the licence as Zlib OR Apache-2.0 OR MIT, and the repository contains LICENSE-APACHE.md, LICENSE-MIT.md and LICENSE-ZLIB.md. The README badge shows only the Zlib licence, so the manifest is the more complete source.

Is tinyvec free of unsafe code?

Yes. The README describes it as a 100% safe crate, safe not just at the public API boundary but for all internal code too, using the forbid attribute on unsafe_code. It states that with tinyvec any uncaught bugs cannot lead to undefined behaviour.

Which features does tinyvec support?

Default features are empty. The alloc feature adds TinyVec, std requires alloc, and there are optional integrations for serde, arbitrary, borsh, generic-array, bin-proto, defmt and schemars, each disabled unless you ask for it. There is also a grab_spare_slice feature for reaching the unused memory of an ArrayVec or SliceVec.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. Lokathor/tinyvec on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lokathor-tinyvec.svg)](https://hysenlabs.com/projects/lokathor-tinyvec)

Was this useful?