Skip to content
Select theme

How to store different types in one Vec

Short answer: Vec<T> holds exactly one type, so the way to store different types in it is to make them one type: an enum with a variant per kind, each variant carrying that kind’s data. Reading a value back out is a match.

Each block below is a complete src/main.rs; the ones that print run with cargo run, the failing one with cargo build. Rust 1.99.0, edition 2024. The project name doesn’t matter — nothing printed depends on it.

Push two different types into one Vec and the compiler stops at the second push:

error[E0308]: mismatched types
--> src/main.rs:4:16
|
3 | items.push(1);
| ----- - this argument has type `{integer}`...
| |
| ... which causes `items` to have type `Vec<{integer}>`
4 | items.push("hello");
| ---- ^^^^^^^ expected integer, found `&str`
| |
| arguments to this method are incorrect
|
note: method defined here
--> /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:995:12
|
995 | pub fn push(&mut self, value: T) {
| ^^^^
For more information about this error, try `rustc --explain E0308`.

The first push pins the whole Vec to Vec<{integer}>. Every element of a Vec has the same type, and by the time the string arrives the compiler has already committed to that one.

An enum is one type with several variants, and each variant can carry its own data:

number 3
text hi

Now the element type is Item, and the first push sets it the same way it does for any inferred Vec — no annotation needed. Each value comes back out through a match, and that is the point as much as the cost: the compiler checks the match covers every variant, so adding a variant later flags every place that reads one.

Variants can hold more than one value, with names:

area: 12.566370614359172
area: 12

area takes a reference and match borrows the fields out of it, so the Vec stays usable after the call. That is the shape of code the enum buys you: one function per behaviour, one arm per variant.

An enum is a closed set: you list the variants, and every match has to say what happens for each one. When the set of types isn’t known where the code is written — types that arrive from a plugin or a config file — the same Vec wants a Box<dyn Trait> instead, which moves the per-type behaviour into impls instead of arms. That is a different trade-off and belongs to its own page.

Next in this chapter: how to use itertools for iterator helpers.