Rust URLs
A web address is a small packed suitcase, all in one string. Add url = "2" to Cargo.toml, and the url crate will unpack it properly.
Splitting an address by hand
String tools leave the port glued to the host. The query stays glued to the path:
let text = "https://shop.example.com:8080/shoes/red?size=9";
let after_scheme = text.split("://").nth(1).unwrap();
let host = after_scheme.split('/').next().unwrap();
let path = &after_scheme[after_scheme.find('/').unwrap()..];
println!("host still has the port = {host}");
println!("path still has the query = {path}");Parse an address
let url = Url::parse("https://shop.example.com:8080/shoes/red?size=9#reviews").unwrap();
println!("scheme = {}", url.scheme());
println!("host = {}", url.host_str().unwrap());
println!("port = {:?}", url.port());
println!("path = {}", url.path());Read the query pairs
query_pairs() splits the query into name and value pairs, already decoded — rust+lang becomes rust lang:
let url = Url::parse("https://example.com/search?q=rust+lang&page=2").unwrap();
for (name, value) in url.query_pairs() {
println!("{} = {}", name, value);
}Join onto a base
join() resolves a relative address against a base, the way a browser resolves a link on a page:
let base = Url::parse("https://example.com/docs/").unwrap();
let page = base.join("guide.html").unwrap();
println!("page = {}", page);The trailing slash trap
Without the trailing slash, the last part of the base counts as a file name — and join replaces it:
let base = Url::parse("https://example.com/docs").unwrap();
let page = base.join("guide.html").unwrap();
println!("page = {}", page);Build a query string
query_pairs_mut() writes pairs for you, encoding spaces and symbols safely:
let mut url = Url::parse("https://example.com/search").unwrap();
url.query_pairs_mut().append_pair("q", "rust lang").append_pair("page", "2");
println!("url = {}", url);Cut pieces off
Pass None to set_query or set_fragment to remove that piece — handy for cleaning a link before you save it:
let mut url = Url::parse("https://example.com/page?x=1#middle").unwrap();
url.set_fragment(None);
url.set_query(None);
println!("clean = {}", url);Example
use url::Url;
fn main() {
let start = Url::parse("https://blog.example.com/posts/42?ref=home#comments").unwrap();
println!("host = {}", start.host_str().unwrap());
println!("path = {}", start.path());
for (name, value) in start.query_pairs() {
println!("query: {} = {}", name, value);
}
println!("fragment = {}", start.fragment().unwrap());
let next = start.join("../about.html").unwrap();
println!("about page = {}", next);
let mut clean = start.clone();
clean.set_fragment(None);
println!("without fragment = {}", clean);
}Key points
- Url::parse returns a Result.
- Text that is not a full address comes back as an Err.
- That means no scheme, or spaces in the wrong place.
- port() returns an Option.
- None means the address uses the default port for its scheme.
- That is 443 for https and 80 for http.
- The query is the part after ?.
- The fragment is the part after #.
- Servers receive the query.
- The fragment stays in the browser and is never sent.
- One parsed Url value holds every piece of the address.
In this example, a blog link is taken apart piece by piece. The pieces are host, path, its one query pair, and the fragment. Then join("../about.html") walks one folder up from /posts/42 and lands on the about page. Finally, a clone of the address has its fragment removed. That is how you would store the link without the scroll position.
Note
A relative address like /docs/page.html cannot be parsed alone — it has no scheme or host.
Join it onto a full base address instead, as the examples below do.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A base address is https://example.com/docs (no trailing slash). What does base.join("guide.html") return?