🎓 A1Learner
🔍

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?