Skip to content
Learn Rust
Theme

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.

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.

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:

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.

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 (rand was 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.
terminal
$ 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:

RustCargo.toml
[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.

Rustsrc/main.rs
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());
}
$ cargo run
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:

Rustsrc/main.rs
fn main() {
let word = "héllo👋🏽";
println!("graphemes: {}", word.graphemes(true).count());
}
$ cargo build
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:

Rustsrc/main.rs
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());
}
$ cargo build
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:

Rustsrc/main.rs
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}");
}
$ cargo run
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.

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.

  • The crate’s front page on docs.rs. Almost every crate opens with an example, and the use lines 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 prelude module. Some big crates offer use some_crate::prelude::*;, which imports all their common traits at once.

Many crates keep optional parts behind features, so you only compile what you use. cargo add lists them, + for on and - for off:

terminal
$ 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:

terminal
$ 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:

terminal
$ cargo tree --depth 1
graphemes v0.1.0 (/home/you/graphemes)
├── serde v1.0.229
└── unicode-segmentation v1.13.3
terminal
$ 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 update moves every crate to the newest version your Cargo.toml allows, and updates Cargo.lock. Add --dry-run to see what it would change.
  • cargo doc --open builds the docs for the versions in your Cargo.lock, from the sources Cargo has already downloaded, and opens them in a browser.

Cargo.toml says which versions are acceptable. Cargo.lock records the exact versions Cargo picked, plus a checksum for each from the registry:

RustCargo.lock
# 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:

terminal
$ 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.

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.

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

Both commands below are run for you, and the outputs shown are real. (The --locked experiment in the lock file section is a third.)

  1. 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)
    
  2. 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
    
Gotcha

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.
Rustsrc/main.rs✗ won't compile
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✓ runs

Go imports use the module path exactly as written, hyphens and all.

Kotlinno equivalent

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.

Python✗ refuses too

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.

TypeScript✓ runs

An import specifier is a string, so import x from "my-pkg" works, and it is the same name you gave npm install.

Java✗ refuses too

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.

C#no equivalent

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.)

Cno equivalent

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.)

C++no equivalent

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.
$ cargo build
error: expected one of `::`, `;`, or `as`, found `-`
 --> src/main.rs:1:12
  |
1 | use unicode-segmentation::UnicodeSegmentation;
  |            ^ expected one of `::`, `;`, or `as`
What happens in Go

Go imports use the module path exactly as written, hyphens and all.

What happens in Kotlin

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.

What happens in Python

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.

What happens in TypeScript

An import specifier is a string, so import x from "my-pkg" works, and it is the same name you gave npm install.

What happens in Java

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.

What happens in C#

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.)

What happens in C

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 happens in C++

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 Rust sees

Crate names may contain hyphens, but names in Rust code can't. A hyphen means minus.

Why Rust says no

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.

Rust, fixedsrc/main.rsRun ↗
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());
}
$ cargo run
bytes: 14
chars: 7
graphemes: 6

Paths in Cargo’s output are shown as /home/you.