Finding and using crates
A Rust library is called a crate. This page covers the whole journey: finding one, checking it’s any good, adding it to your project, and using it in your code, including what that use line at the top really does.
Remembercargo add puts a crate in Cargo.toml. use gives you short names for what’s inside it. You never need mod for a crate.
Where crates live
Section titled “Where crates live”| Site | What it’s for |
|---|---|
| crates.io | The Rust community’s package registry, and the one cargo add installs from unless you configure another. |
| docs.rs | Documentation for the crates on crates.io, built automatically when a version is published. |
| lib.rs | Another site for browsing crates. blessed.rs describes it as a semi-curated directory. |
| blessed.rs | An unofficial, hand-curated guide to the ecosystem: for common jobs, which crates to look at. Start here when you don’t know what to search for. |
Also see The crates everyone uses, which maps common jobs to the usual crate and to what you’d use in other languages.
Finding one
Section titled “Finding one”Say you want to count letters the way a person would, where a waving hand with a skin tone counts as one. In Unicode terms, those are graphemes. You can search from the terminal:
$ cargo search grapheme --limit 5
grapheme = "1.3.0" # Abstractions for working with extended Unicode grapheme clusters
grapheme-utils = "0.1.2" # Handy utils for working with utf-8 [unicode] Extended Grapheme Clusters
fast-grapheme-segmenter = "0.1.1" # Fast, lightweight, forward-only Unicode extended grapheme cluster segmentation (UAX #29)
grapheme-cluster-utils = "0.1.1" # Unicode-safe String manipulation utilities by extended grapheme cluster (UAX #29).
grapheme_macro = "4.0.1" # Macros for the `grapheme` crate
... and 489 crates more (use --limit N to see more)
note: to learn more about a package, run `cargo info <name>`
In this run, unicode-segmentation, the crate this page uses for the job, isn’t in the top five, and the list changes as crates are published. The registry’s ranking tells you which crates match your words, not which one to trust. A look at blessed.rs, lib.rs or a web search is a better start when you don’t know the name.
Is it any good?
Section titled “Is it any good?”Before you add a crate, take thirty seconds on its crates.io page:
- Downloads, especially recent ones. A crate many people use has had more eyes on its bugs, though a download count is not a quality score.
- Last release. Months is fine. Years can be fine for small, finished crates, but check the issues.
- Documentation on docs.rs, with examples.
- Dependents: how many other crates use it.
- Version number. Many well-known crates stay below 1.0 for years (
randwas at 0.10 when this page was written). That’s common in Rust, and it only means the authors haven’t promised a stable API yet.
Adding it
Section titled “Adding it”$ cargo add unicode-segmentation
Updating crates.io index
Adding unicode-segmentation v1.13.3 to dependencies
Features:
- no_std
Updating crates.io index
Locking 1 package to highest Rust 1.99.1 compatible version
Adding unicode-segmentation v1.13.3
Cargo wrote the dependency into Cargo.toml for you:
[package]name = "graphemes"version = "0.1.0"edition = "2024"
[dependencies]unicode-segmentation = "1.13.3""1.13.3" means “1.13.3 or any newer 1.x” (Cargo calls this a caret requirement; see Specifying dependencies). Crates are expected to follow semantic versioning (a convention where only a new major version may break your code), so those updates shouldn’t break it. The exact version you got is recorded in Cargo.lock.
Using it in your code
Section titled “Using it in your code”use unicode_segmentation::UnicodeSegmentation;
fn main() { let word = "héllo👋🏽"; println!("bytes: {}", word.len()); println!("chars: {}", word.chars().count()); // what a person would call "letters": the hand and its skin tone count as one println!("graphemes: {}", word.graphemes(true).count());}bytes: 14 chars: 7 graphemes: 6
That first line is the confusing one, because UnicodeSegmentation never appears again. Here it is piece by piece:
| Piece | What it is |
|---|---|
use |
“Let me refer to this by a short name.” It doesn’t load or install anything. |
unicode_segmentation |
The crate’s name, with - turned into _. Every normal dependency in your Cargo.toml is available by name in every file. No mod line needed. |
:: |
“Inside of”, like . in a Java or Python import path. |
UnicodeSegmentation |
A trait inside the crate: a set of methods it adds to existing types. This one adds .graphemes() to every string. |
So the line doesn’t give you a function to call. It switches on a method. In Rust, a trait’s methods only work where the trait is in scope, which usually means imported with use in that file (or module). That’s why you never write UnicodeSegmentation again: you just call word.graphemes(true), and it works because the trait is in scope.
Leave the line out, and the method simply doesn’t exist:
fn main() { let word = "héllo👋🏽"; println!("graphemes: {}", word.graphemes(true).count());}error[E0599]: no method named `graphemes` found for reference `&str` in the current scope --> src/main.rs:3:36 | 3 | println!("graphemes: {}", word.graphemes(true).count()); | ^^^^^^^^^ method not found in `&str`
For a standard library trait the compiler suggests the use line. It did not above, because nothing in the program mentioned the crate yet. Once any file in the crate does, it can:
use unicode_segmentation::GraphemeCursor;
fn main() { let word = "héllo👋🏽"; // the crate is mentioned here, but the trait that adds `.graphemes()` is not imported let _cursor = GraphemeCursor::new(0, word.len(), true); println!("graphemes: {}", word.graphemes(true).count());}error[E0599]: no method named `graphemes` found for reference `&str` in the current scope --> src/main.rs:7:36 | 7 | println!("graphemes: {}", word.graphemes(true).count()); | ^^^^^^^^^ | = help: items from traits can only be used if the trait is in scope help: trait `UnicodeSegmentation` which provides `graphemes` is implemented but not in scope; perhaps you want to import it | 1 + use unicode_segmentation::UnicodeSegmentation; | help: there is a method `grapheme_indices` with a similar name | 7 | println!("graphemes: {}", word.grapheme_indices(true).count()); | +++++++
Otherwise, look at the crate’s docs (next section).
You can also skip the use line and call the method through its full path. It’s wordier, but it shows exactly where the method comes from:
fn main() { let word = "héllo👋🏽"; // no `use` line: call the trait's method through its full path let n = unicode_segmentation::UnicodeSegmentation::graphemes(word, true).count(); println!("graphemes: {n}");}graphemes: 6
You’ll also see use some_crate::SomeTrait as _;. That imports the trait’s methods without taking up the name, which is handy when two traits have the same name.
If you’ve seen this before
Section titled “If you’ve seen this before”C# works the same way: LINQ’s .Where() and .Select() are extension methods, and only exist where System.Linq is imported (new projects import it for you). So do Kotlin extension functions from a library, which you import by name. Go, Java and Python don’t have this: there, a method belongs to its type and is always there.
How do I know what to import?
Section titled “How do I know what to import?”- The crate’s front page on docs.rs. Almost every crate opens with an example, and the
uselines at the top of it are what you need. - The “Traits” section on docs.rs. If a crate adds methods to types you already have, they come from a trait listed there.
- Your editor. rust-analyzer has a setting,
completion.autoimport.enable(on by default), for “completions that automatically add imports when completed”. Your editor has to support it. - The
preludemodule. Some big crates offeruse some_crate::prelude::*;, which imports all their common traits at once.
Features, updates and removing
Section titled “Features, updates and removing”Many crates keep optional parts behind features, so you only compile what you use. cargo add lists them, + for on and - for off:
$ cargo add serde --features derive
Updating crates.io index
Adding serde v1.0.229 to dependencies
Features:
+ derive
+ serde_derive
+ std
- alloc
- rc
- unstable
Updating crates.io index
Locking 7 packages to highest Rust 1.99.1 compatible versions
Adding proc-macro2 v1.0.107
Adding quote v1.0.47
Adding serde v1.0.229
Adding serde_core v1.0.229
Adding serde_derive v1.0.229
Adding syn v3.0.9
Adding unicode-ident v1.0.27
The features you turned on are written into Cargo.toml:
$ cat Cargo.toml
[package]
name = "graphemes"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1.0.229", features = ["derive"] }
unicode-segmentation = "1.13.3"
See what your project depends on, and take a crate out again:
$ cargo tree --depth 1
graphemes v0.1.0 (/home/you/graphemes)
├── serde v1.0.229
└── unicode-segmentation v1.13.3
$ cargo remove serde
Removing serde from dependencies
cargo tree --invert <crate> turns the tree around and shows who depends on a crate (try it below).
cargo updatemoves every crate to the newest version yourCargo.tomlallows, and updatesCargo.lock. Add--dry-runto see what it would change.cargo doc --openbuilds the docs for the versions in yourCargo.lock, from the sources Cargo has already downloaded, and opens them in a browser.
The lock file
Section titled “The lock file”Cargo.toml says which versions are acceptable. Cargo.lock records the exact versions Cargo picked, plus a checksum for each from the registry:
# This file is automatically @generated by Cargo.# It is not intended for manual editing.version = 4
[[package]]name = "graphemes"version = "0.1.0"dependencies = [ "unicode-segmentation",]
[[package]]name = "unicode-segmentation"version = "1.13.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"Cargo changes that file when Cargo.toml asks for something it doesn’t cover, when you run cargo update or cargo remove, or when the package’s own version changes. For a program like this one, commit Cargo.lock, so everyone (and your CI, the server that builds every change) builds with the same versions. The --locked flag makes that strict. Here Cargo.toml was edited by hand to add itoa, but Cargo.lock wasn’t updated:
$ cargo build --locked error: cannot update the lock file /home/you/graphemes/Cargo.lock because --locked was passed to prevent this help: to generate the lock file without accessing the network, remove the --locked flag and use --offline instead. $ cargo build $ grep -A 3 'name = "itoa"' Cargo.lock name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" $ cargo build --locked
A CI job can pass --locked so that a build which would change the lock file fails instead.
Security checks and bigger projects
Section titled “Security checks and bigger projects”cargo audit checks your Cargo.lock against the RustSec database of reported vulnerabilities. It is a separate tool (cargo install cargo-audit). I did not run it for this page, so there is no session for it.
When a project grows to several packages, a workspace lets them share one Cargo.lock and one target/ folder, so they resolve versions together. Modules and Visibility explains packages and crates, and Several programs builds a workspace step by step.
The same thing in your language
Section titled “The same thing in your language”| Language | Manifest | Lock file | Add a package | Registry |
|---|---|---|---|---|
| Rust | Cargo.toml |
Cargo.lock |
cargo add serde |
crates.io |
| Go | go.mod |
go.mod records the versions; go.sum holds checksums |
go get example.com/pkg |
Go module proxy (by default) |
| Python | pyproject.toml |
depends on the tool (uv.lock, poetry.lock) |
uv add requests |
PyPI |
| TypeScript | package.json |
package-lock.json (npm), bun.lock |
npm install zod |
npm |
| Java | pom.xml or build.gradle |
none by default | edit the build file | Maven Central |
| Kotlin | build.gradle.kts |
none by default | edit the build file | Maven Central |
| C# | .csproj |
packages.lock.json, opt-in |
dotnet add package Serilog |
NuGet |
| C | CMakeLists.txt plus a package manager such as vcpkg or Conan |
per tool | vcpkg add port zlib |
no single standard |
| C++ | CMakeLists.txt plus vcpkg or Conan |
per tool | vcpkg add port fmt |
no single standard |
Try it yourself
Section titled “Try it yourself”Both commands below are run for you, and the outputs shown are real. (The --locked experiment in the lock file section is a third.)
-
Ask who needs a crate. Run
cargo tree --invert unicode-segmentation. Look for your own package indented below the crate, as the only thing that depends on it.terminal$ cargo tree --invert unicode-segmentation unicode-segmentation v1.13.3 └── graphemes v0.1.0 (/home/you/graphemes) -
See whether anything is newer. Run
cargo update --dry-run. In this run nothing was listed, only a warning that the lock file was not written. If a newer compatible release has appeared since, it appears as a line.terminal$ cargo update --dry-run warning: not updating lockfile due to dry run
Writing the crate name with a hyphen
Looks right at a glance, but Rust won't compile it.
Works in Go. In Rust it's just a rule.No direct equivalent in Kotlin. Just a Rust rule.Python refuses this too. Same idea, different words.Works in TypeScript. In Rust it's just a rule.Java refuses this too. Same idea, different words.No direct equivalent in C#. Just a Rust rule.No direct equivalent in C. Just a Rust rule.No direct equivalent in C++. Just a Rust rule.use unicode-segmentation::UnicodeSegmentation;
fn main() { let word = "héllo👋🏽"; println!("bytes: {}", word.len()); println!("chars: {}", word.chars().count()); // what a person would call "letters": the hand and its skin tone count as one println!("graphemes: {}", word.graphemes(true).count());}Go imports use the module path exactly as written, hyphens and all.
An import can't contain a hyphen, and the build coordinate you install is written separately from the import: kotlinx-coroutines-core is imported as kotlinx.coroutines.
import unicode-segmentation is a SyntaxError in Python (I ran it). The name you pip install is also often not the name you import: python-dateutil is imported as dateutil.
An import specifier is a string, so import x from "my-pkg" works, and it is the same name you gave npm install.
A hyphen is not allowed in a Java package or class name: import a-b.C; fails with '.' expected (I ran it). The Maven coordinate you add to the build is written separately from the package you import.
A namespace is a dotted list of identifiers, which can't contain a hyphen. The NuGet package id you install is written separately from the using line. (I did not run this one.)
There is no package import: a header's file name may contain hyphens, as in #include <my-lib.h>. (I did not run this one.)
There is no package import: a header's file name may contain hyphens, as in #include <my-lib.h>. (I did not run this one.)
What does the compiler say? Guess first, then open.
error: expected one of `::`, `;`, or `as`, found `-` --> src/main.rs:1:12 | 1 | use unicode-segmentation::UnicodeSegmentation; | ^ expected one of `::`, `;`, or `as`
Go imports use the module path exactly as written, hyphens and all.
An import can't contain a hyphen, and the build coordinate you install is written separately from the import: kotlinx-coroutines-core is imported as kotlinx.coroutines.
import unicode-segmentation is a SyntaxError in Python (I ran it). The name you pip install is also often not the name you import: python-dateutil is imported as dateutil.
An import specifier is a string, so import x from "my-pkg" works, and it is the same name you gave npm install.
A hyphen is not allowed in a Java package or class name: import a-b.C; fails with '.' expected (I ran it). The Maven coordinate you add to the build is written separately from the package you import.
A namespace is a dotted list of identifiers, which can't contain a hyphen. The NuGet package id you install is written separately from the using line. (I did not run this one.)
There is no package import: a header's file name may contain hyphens, as in #include <my-lib.h>. (I did not run this one.)
There is no package import: a header's file name may contain hyphens, as in #include <my-lib.h>. (I did not run this one.)
Crate names may contain hyphens, but names in Rust code can't. A hyphen means minus.
So Cargo quietly turns every - into _ for your code: the crate unicode-segmentation is unicode_segmentation inside Rust. You'll see this with tokio-util (tokio_util), async-trait (async_trait) and many others. A crate that is already published with an underscore, like serde_json, needs no change.
The fix
Compiles and runs.
use unicode_segmentation::UnicodeSegmentation;
fn main() { let word = "héllo👋🏽"; println!("bytes: {}", word.len()); println!("chars: {}", word.chars().count()); // what a person would call "letters": the hand and its skin tone count as one println!("graphemes: {}", word.graphemes(true).count());}bytes: 14 chars: 7 graphemes: 6
Read more
Section titled “Read more”- The Cargo Book: Features, Cargo.toml vs Cargo.lock (also covers libraries), and the
cargo treeandcargo addcommands. - The Rust Book: Bringing paths into scope with
useand Cargo workspaces. UnicodeSegmentationon docs.rs, andcargo-audit.
Paths in Cargo’s output are shown as /home/you.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.