What is rustc?

Welcome to "The rustc book"! rustc is the compiler for the Rust programming language, provided by the project itself. Compilers take your source code and produce binary code, either as a library or executable.

Most Rust programmers don't invoke rustc directly, but instead do it through Cargo. It's all in service of rustc though! If you want to see how Cargo calls rustc, you can

$ cargo build --verbose

And it will print out each rustc invocation. This book can help you understand what each of these options does. Additionally, while most Rustaceans use Cargo, not all do: sometimes they integrate rustc into other build systems. This book should provide a guide to all of the options you'd need to do so.

Basic usage

Let's say you've got a little hello world program in a file hello.rs:

fn main() {
    println!("Hello, world!");
}

To turn this source code into an executable, you can use rustc:

$ rustc hello.rs
$ ./hello # on a *NIX
$ .\hello.exe # on Windows

Note that we only ever pass rustc the crate root, not every file we wish to compile. For example, if we had a main.rs that looked like this:

mod foo;

fn main() {
    foo::hello();
}

And a foo.rs that had this:

fn hello() {
    println!("Hello, world!");
}

To compile this, we'd run this command:

$ rustc main.rs

No need to tell rustc about foo.rs; the mod statements give it everything that it needs. This is different than how you would use a C compiler, where you invoke the compiler on each file, and then link everything together. In other words, the crate is a translation unit, not a particular module.

Command-line arguments

Here's a list of command-line arguments to rustc and what they do.

-h/--help: get help

This flag will print out help information for rustc.

--cfg: configure the compilation environment

This flag can turn on or off various #[cfg] settings.

The value can either be a single identifier or two identifiers separated by =.

For examples, --cfg 'verbose' or --cfg 'feature="serde"'. These correspond to #[cfg(verbose)] and #[cfg(feature = "serde")] respectively.

-L: add a directory to the library search path

When looking for external crates, a directory passed to this flag will be searched.

-l: link the generated crate to a native library

This flag allows you to specify linking to a specific native library when building a crate.

--crate-type: a list of types of crates for the compiler to emit

This instructs rustc on which crate type to build.

--crate-name: specify the name of the crate being built

This informs rustc of the name of your crate.

--emit: emit output other than a crate

Instead of producing a crate, this flag can print out things like the assembly or LLVM-IR.

--print: print compiler information

This flag prints out various information about the compiler.

-g: include debug information

A synonym for -C debug-level=2.

-O: optimize your code

A synonym for -C opt-level=2.

-o: filename of the output

This flag controls the output filename.

--out-dir: directory to write the output in

The outputted crate will be written to this directory.

--explain: provide a detailed explanation of an error message

Each error of rustc's comes with an error code; this will print out a longer explanation of a given error.

--test: build a test harness

When compiling this crate, rustc will ignore your main function and instead produce a test harness.

--target: select a target triple to build

This controls which target to produce.

-W: set lint warnings

This flag will set which lints should be set to the warn level.

-A: set lint allowed

This flag will set which lints should be set to the allow level.

-D: set lint denied

This flag will set which lints should be set to the deny level.

-F: set lint forbidden

This flag will set which lints should be set to the forbid level.

--cap-lints: set the most restrictive lint level

This flag lets you 'cap' lints, for more, see here.

-C/--codegen: code generation options

This flag will allow you to set codegen options.

-V/--version: print a version

This flag will print out rustc's version.

-v/--verbose: use verbose output

This flag, when combined with other flags, makes them produce extra output.

--extern: specify where an external library is located

This flag allows you to pass the name and location of an external crate that will be linked into the crate you're buildling.

--sysroot: Override the system root

The "sysroot" is where rustc looks for the crates that come with the Rust distribution; this flag allows that to be overridden.

--error-format: control how errors are produced

This flag lets you control the format of errors.

--color: configure coloring of output

This flag lets you control color settings of the output.

Lints

In software, a "lint" is a tool used to help improve your source code. The Rust compiler contains a number of lints, and when it compiles your code, it will also run the lints. These lints may produce a warning, an error, or nothing at all, depending on how you've configured things.

Here's a small example:

$ cat main.rs
fn main() {
    let x = 5;
}
$ rustc main.rs
warning: unused variable: `x`
 --> main.rs:2:9
  |
2 |     let x = 5;
  |         ^
  |
  = note: #[warn(unused_variables)] on by default
  = note: to avoid this warning, consider using `_x` instead

This is the unused_variables lint, and it tells you that you've introduced a variable that you don't use in your code. That's not wrong, so it's not an error, but it might be a bug, so you get a warning.

Lint levels

In rustc, lints are divided into four levels:

  1. allow
  2. warn
  3. deny
  4. forbid

Each lint has a default level (explained in the lint listing later in this chapter), and the compiler has a default warning level. First, let's explain what these levels mean, and then we'll talk about configuration.

allow

These lints exist, but by default, do nothing. For example, consider this source:


# #![allow(unused_variables)]
#fn main() {
pub fn foo() {}
#}

Compiling this file produces no warnings:

$ rustc lib.rs --crate-type=lib
$

But this code violates the missing_docs lint.

These lints exist mostly to be manually turned on via configuration, as we'll talk about later in this section.

warn

The 'warn' lint level will produce a warning if you violate the lint. For example, this code runs afoul of the unused_variable lint:


# #![allow(unused_variables)]
#fn main() {
pub fn foo() {
    let x = 5;
}
#}

This will produce this warning:

$ rustc lib.rs --crate-type=lib
warning: unused variable: `x`
 --> lib.rs:2:9
  |
2 |     let x = 5;
  |         ^
  |
  = note: #[warn(unused_variables)] on by default
  = note: to avoid this warning, consider using `_x` instead

deny

A 'deny' lint produces an error if you violate it. For example, this code runs into the exceeding_bitshifts lint.

fn main() {
    100u8 << 10;
}
$ rustc main.rs
error: bitshift exceeds the type's number of bits
 --> main.rs:2:13
  |
2 |     100u8 << 10;
  |     ^^^^^^^^^^^
  |
  = note: #[deny(exceeding_bitshifts)] on by default

What's the difference between an error from a lint and a regular old error? Lints are configurable via levels, so in a similar way to 'allow' lints, warnings that are 'deny' by default let you allow them. Similarly, you may wish to set up a lint that is warn by default to produce an error instead. This lint level gives you that.

forbid

'forbid' is a special lint level that's stronger than 'deny'. It's the same as 'deny' in that a lint at this level will produce an error, but unlike the 'deny' level, the 'forbid' level can not be overridden to be anything lower than an error.

Configuring warning levels

Remember our missing_docs example from the 'allow' lint level?

$ cat lib.rs
pub fn foo() {}
$ rustc lib.rs --crate-type=lib
$

We can configure this lint to operate at a higher level, both with compiler flags, as well as with an attribute in the source code.

You can also "cap" lints so that the compiler can choose to ignore certain lint levels. We'll talk about that last.

Via compiler flag

The -A, -W, -D, and -F flags let you turn one or more lints into allowed, warning, deny, or forbid levels, like this:

$ rustc lib.rs --crate-type=lib -W missing-docs
warning: missing documentation for crate
 --> lib.rs:1:1
  |
1 | pub fn foo() {}
  | ^^^^^^^^^^^^
  |
  = note: requested on the command line with `-W missing-docs`

warning: missing documentation for a function
 --> lib.rs:1:1
  |
1 | pub fn foo() {}
  | ^^^^^^^^^^^^
$ rustc lib.rs --crate-type=lib -D missing-docs
error: missing documentation for crate
 --> lib.rs:1:1
  |
1 | pub fn foo() {}
  | ^^^^^^^^^^^^
  |
  = note: requested on the command line with `-D missing-docs`

error: missing documentation for a function
 --> lib.rs:1:1
  |
1 | pub fn foo() {}
  | ^^^^^^^^^^^^

error: aborting due to 2 previous errors

You can also pass each flag more than once for changing multiple lints:

$ rustc lib.rs --crate-type=lib -D missing-docs -D unused-variables

And of course, you can mix these four flags together:

$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables

Via an attribute

You can also modify the lint level with a crate-wide attribute:

$ cat lib.rs
#![warn(missing_docs)]

pub fn foo() {}
$ rustc lib.rs --crate-type=lib
warning: missing documentation for crate
 --> lib.rs:1:1
  |
1 | / #![warn(missing_docs)]
2 | |
3 | | pub fn foo() {}
  | |_______________^
  |
note: lint level defined here
 --> lib.rs:1:9
  |
1 | #![warn(missing_docs)]
  |         ^^^^^^^^^^^^

warning: missing documentation for a function
 --> lib.rs:3:1
  |
3 | pub fn foo() {}
  | ^^^^^^^^^^^^

All four, warn, allow, deny, and forbid all work this way.

You can also pass in multiple lints per attribute:


# #![allow(unused_variables)]
#![warn(missing_docs, unused_variables)]

#fn main() {
pub fn foo() {}
#}

And use multiple attributes together:


# #![allow(unused_variables)]
#![warn(missing_docs)]
#![deny(unused_variables)]

#fn main() {
pub fn foo() {}
#}

Capping lints

rustc supports a flag, --cap-lints LEVEL that sets the "lint cap level." This is the maximum level for all lints. So for example, if we take our code sample from the "deny" lint level above:

fn main() {
    100u8 << 10;
}

And we compile it, capping lints to warn:

$ rustc lib.rs --cap-lints warn
warning: bitshift exceeds the type's number of bits
 --> lib.rs:2:5
  |
2 |     100u8 << 10;
  |     ^^^^^^^^^^^
  |
  = note: #[warn(exceeding_bitshifts)] on by default

warning: this expression will panic at run-time
 --> lib.rs:2:5
  |
2 |     100u8 << 10;
  |     ^^^^^^^^^^^ attempt to shift left with overflow

It now only warns, rather than errors. We can go further and allow all lints:

$ rustc lib.rs --cap-lints allow
$

This feature is used heavily by Cargo; it will pass --cap-lints allow when compiling your dependencies, so that if they have any warnings, they do not pollute the output of your build.

Lint Groups

rustc has the concept of a "lint group", where you can toggle several warnings through one name.

For example, the nonstandard-style lint sets non-camel-case-types, non-snake-case, and non-upper-case-globals all at once. So these are equivalent:

$ rustc -D nonstandard-style
$ rustc -D non-camel-case-types -D non-snake-case -D non-upper-case-globals

Here's a list of each lint group, and the lints that they are made up of:

group description lints
nonstandard-style Violation of standard naming conventions non-camel-case-types, non-snake-case, non-upper-case-globals
warnings all lints that would be issuing warnings all lints that would be issuing warnings
edition-2018 Lints that will be turned into errors in Rust 2018 tyvar-behind-raw-pointer
rust-2018-idioms Lints to nudge you toward idiomatic features of Rust 2018 bare-trait-object, unreachable-pub
unused These lints detect things being declared but not used unused-imports, unused-variables, unused-assignments, dead-code, unused-mut, unreachable-code, unreachable-patterns, unused-must-use, unused-unsafe, path-statements, unused-attributes, unused-macros, unused-allocation, unused-doc-comment, unused-extern-crates, unused-features, unused-parens
future-incompatible Lints that detect code that has future-compatibility problems private-in-public, pub-use-of-private-extern-crate, patterns-in-fns-without-body, safe-extern-statics, invalid-type-param-default, legacy-directory-ownership, legacy-imports, legacy-constructor-visibility, missing-fragment-specifier, illegal-floating-point-literal-pattern, anonymous-parameters, parenthesized-params-in-types-and-modules, late-bound-lifetime-arguments, safe-packed-borrows, incoherent-fundamental-impls, tyvar-behind-raw-pointer, unstable-name-collision

Additionally, there's a bad-style lint group that's a deprecated alias for nonstandard-style.

Finally, you can also see the table above by invoking rustc -W help. This will give you the exact values for the specific compiler you have installed.

Lint listing

This section lists out all of the lints, grouped by their default lint levels.

You can also see this list by running rustc -W help.

Allowed-by-default lints

These lints are all set to the 'allow' level by default. As such, they won't show up unless you set them to a higher lint level with a flag or attribute.

anonymous-parameters

This lint detects anonymous parameters. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(usize);
}
#}

When set to 'deny', this will produce:

error: use of deprecated anonymous parameter
 --> src/lib.rs:5:11
  |
5 |     fn foo(usize);
  |           ^
  |
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #41686 <https://github.com/rust-lang/rust/issues/41686>

This syntax is mostly a historical accident, and can be worked around quite easily:


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(_: usize);
}
#}

bare-trait-object

This lint suggests using dyn Trait for trait objects. Some example code that triggers this lint:


# #![allow(unused_variables)]
#![feature(dyn_trait)]

#fn main() {
trait Trait { }

fn takes_trait_object(_: Box<Trait>) {
}
#}

When set to 'deny', this will produce:

error: trait objects without an explicit `dyn` are deprecated
 --> src/lib.rs:7:30
  |
7 | fn takes_trait_object(_: Box<Trait>) {
  |                              ^^^^^ help: use `dyn`: `dyn Trait`
  |

To fix it, do as the help message suggests:


# #![allow(unused_variables)]
#![feature(dyn_trait)]
#![deny(bare_trait_objects)]

#fn main() {
trait Trait { }

fn takes_trait_object(_: Box<dyn Trait>) {
}
#}

box-pointers

This lints use of the Box type. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
struct Foo {
    x: Box<isize>,
}
#}

When set to 'deny', this will produce:

error: type uses owned (Box type) pointers: std::boxed::Box<isize>
 --> src/lib.rs:6:5
  |
6 |     x: Box<isize> //~ ERROR type uses owned
  |     ^^^^^^^^^^^^^
  |

This lint is mostly historical, and not particularly useful. Box<T> used to be built into the language, and the only way to do heap allocation. Today's Rust can call into other allocators, etc.

elided-lifetime-in-path

This lint detects the use of hidden lifetime parameters. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
struct Foo<'a> {
    x: &'a u32
}

fn foo(x: &Foo) {
}
#}

When set to 'deny', this will produce:

error: hidden lifetime parameters are deprecated, try `Foo<'_>`
 --> src/lib.rs:5:12
  |
5 | fn foo(x: &Foo) {
  |            ^^^
  |

Lifetime elision elides this lifetime, but that is being deprecated.

missing-copy-implementations

This lint detects potentially-forgotten implementations of Copy. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
pub struct Foo {
    pub field: i32
}
#}

When set to 'deny', this will produce:

error: type could implement `Copy`; consider adding `impl Copy`
 --> src/main.rs:3:1
  |
3 | / pub struct Foo { //~ ERROR type could implement `Copy`; consider adding `impl Copy`
4 | |     pub field: i32
5 | | }
  | |_^
  |

You can fix the lint by deriving Copy.

This lint is set to 'allow' because this code isn't bad; it's common to write newtypes like this specifically so that a Copy type is no longer Copy.

missing-debug-implementations

This lint detects missing implementations of fmt::Debug. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
pub struct Foo;
#}

When set to 'deny', this will produce:

error: type does not implement `fmt::Debug`; consider adding #[derive(Debug)] or a manual implementation
 --> src/main.rs:3:1
  |
3 | pub struct Foo;
  | ^^^^^^^^^^^^^^^
  |

You can fix the lint by deriving Debug.

missing-docs

This lint detects missing documentation for public items. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
pub fn foo() {}
#}

When set to 'deny', this will produce:

error: missing documentation for crate
 --> src/main.rs:1:1
  |
1 | / #![deny(missing_docs)]
2 | |
3 | | pub fn foo() {}
4 | |
5 | | fn main() {}
  | |____________^
  |

error: missing documentation for a function
 --> src/main.rs:3:1
  |
3 | pub fn foo() {}
  | ^^^^^^^^^^^^

To fix the lint, add documentation to all items.

single-use-lifetime

This lint detects lifetimes that are only used once. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
struct Foo<'x> {
    x: &'x u32
}
#}

When set to 'deny', this will produce:

error: lifetime name `'x` only used once
 --> src/main.rs:3:12
  |
3 | struct Foo<'x> {
  |            ^^
  |

trivial-casts

This lint detects trivial casts which could be removed. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x: &u32 = &42;
let _ = x as *const u32;
#}

When set to 'deny', this will produce:

error: trivial cast: `&u32` as `*const u32`. Cast can be replaced by coercion, this might require type ascription or a temporary variable
 --> src/main.rs:5:13
  |
5 |     let _ = x as *const u32;
  |             ^^^^^^^^^^^^^^^
  |
note: lint level defined here
 --> src/main.rs:1:9
  |
1 | #![deny(trivial_casts)]
  |         ^^^^^^^^^^^^^

trivial-numeric-casts

This lint detects trivial casts of numeric types which could be removed. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x = 42i32 as i32;
#}

When set to 'deny', this will produce:

error: trivial numeric cast: `i32` as `i32`. Cast can be replaced by coercion, this might require type ascription or a temporary variable
 --> src/main.rs:4:13
  |
4 |     let x = 42i32 as i32;
  |             ^^^^^^^^^^^^
  |

unreachable-pub

This lint triggers for pub items not reachable from the crate root. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
mod foo {
    pub mod bar {
        
    }
}
#}

When set to 'deny', this will produce:

error: unreachable `pub` item
 --> src/main.rs:4:5
  |
4 |     pub mod bar {
  |     ---^^^^^^^^
  |     |
  |     help: consider restricting its visibility: `pub(crate)`
  |

unsafe-code

This lint catches usage of unsafe code. Some example code that triggers this lint:

fn main() {
    unsafe {

    }
}

When set to 'deny', this will produce:

error: usage of an `unsafe` block
 --> src/main.rs:4:5
  |
4 | /     unsafe {
5 | |         
6 | |     }
  | |_____^
  |

unstable-features

This lint is deprecated and no longer used.

unused-extern-crates

This lint guards against extern crate items that are never used. Some example code that triggers this lint:

extern crate semver;

When set to 'deny', this will produce:

error: unused extern crate
 --> src/main.rs:3:1
  |
3 | extern crate semver;
  | ^^^^^^^^^^^^^^^^^^^^
  |

unused-import-braces

This lint catches unnecessary braces around an imported item. Some example code that triggers this lint:

use test::{A};

pub mod test {
    pub struct A;
}
# fn main() {}

When set to 'deny', this will produce:

error: braces around A is unnecessary
 --> src/main.rs:3:1
  |
3 | use test::{A};
  | ^^^^^^^^^^^^^^
  |

To fix it, use test::A;

unused-qualifications

This lint detects unnecessarily qualified names. Some example code that triggers this lint:

mod foo {
    pub fn bar() {}
}

fn main() {
    use foo::bar;
    foo::bar();
}

When set to 'deny', this will produce:

error: unnecessary qualification
 --> src/main.rs:9:5
  |
9 |     foo::bar();
  |     ^^^^^^^^
  |

You can call bar() directly, without the foo::.

unused-results

This lint checks for the unused result of an expression in a statement. Some example code that triggers this lint:

fn foo<T>() -> T { panic!() }

fn main() {
    foo::<usize>();
}

When set to 'deny', this will produce:

error: unused result
 --> src/main.rs:6:5
  |
6 |     foo::<usize>();
  |     ^^^^^^^^^^^^^^^
  |

variant-size-differences

This lint detects enums with widely varying variant sizes. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
enum En {
    V0(u8),
    VBig([u8; 1024]),
}
#}

When set to 'deny', this will produce:

error: enum variant is more than three times larger (1024 bytes) than the next largest
 --> src/main.rs:5:5
  |
5 |     VBig([u8; 1024]),   //~ ERROR variant is more than three times larger
  |     ^^^^^^^^^^^^^^^^
  |

Warn-by-default lints

These lints are all set to the 'warn' level by default.

const-err

This lint detects an erroneous expression while doing constant evaluation. Some example code that triggers this lint:

let b = 200u8 + 200u8;

This will produce:

warning: attempt to add with overflow
 --> src/main.rs:2:9
  |
2 | let b = 200u8 + 200u8;
  |         ^^^^^^^^^^^^^
  |

dead-code

This lint detects detect unused, unexported items. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
fn foo() {}
#}

This will produce:

warning: function is never used: `foo`
 --> src/lib.rs:2:1
  |
2 | fn foo() {}
  | ^^^^^^^^
  |

deprecated

This lint detects detects use of deprecated items. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
#[deprecated]
fn foo() {}

fn bar() {
    foo();
}
#}

This will produce:

warning: use of deprecated item 'foo'
 --> src/lib.rs:7:5
  |
7 |     foo();
  |     ^^^
  |

illegal-floating-point-literal-pattern

This lint detects floating-point literals used in patterns. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x = 42.0;

match x {
    5.0 => {},
    _ => {},
}
#}

This will produce:

warning: floating-point literals cannot be used in patterns
 --> src/main.rs:4:9
  |
4 |         5.0 => {},
  |         ^^^
  |
  = note: #[warn(illegal_floating_point_literal_pattern)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #41620 <https://github.com/rust-lang/rust/issues/41620>

improper-ctypes

This lint detects proper use of libc types in foreign modules. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
extern "C" {
    static STATIC: String;
}
#}

This will produce:

warning: found struct without foreign-function-safe representation annotation in foreign module, consider adding a #[repr(C)] attribute to the type
 --> src/main.rs:2:20
  |
2 |     static STATIC: String;
  |                    ^^^^^^
  |

late-bound-lifetime-arguments

This lint detects detects generic lifetime arguments in path segments with late bound lifetime parameters. Some example code that triggers this lint:

struct S;

impl S {
    fn late<'a, 'b>(self, _: &'a u8, _: &'b u8) {}
}

fn main() {
    S.late::<'static>(&0, &0);
}

This will produce:

warning: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present
 --> src/main.rs:8:14
  |
4 |     fn late<'a, 'b>(self, _: &'a u8, _: &'b u8) {}
  |             -- the late bound lifetime parameter is introduced here
...
8 |     S.late::<'static>(&0, &0);
  |              ^^^^^^^
  |
  = note: #[warn(late_bound_lifetime_arguments)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #42868 <https://github.com/rust-lang/rust/issues/42868>

non-camel-case-types

This lint detects types, variants, traits and type parameters that don't have camel case names. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
struct s;
#}

This will produce:

warning: type `s` should have a camel case name such as `S`
 --> src/main.rs:1:1
  |
1 | struct s;
  | ^^^^^^^^^
  |

non-shorthand-field-patterns

This lint detects using Struct { x: x } instead of Struct { x } in a pattern. Some example code that triggers this lint:

struct Point {
    x: i32,
    y: i32,
}


fn main() {
    let p = Point {
        x: 5,
        y: 5,
    };

    match p {
        Point { x: x, y: y } => (),
    }
}

This will produce:

warning: the `x:` in this pattern is redundant
  --> src/main.rs:14:17
   |
14 |         Point { x: x, y: y } => (),
   |                 --^^
   |                 |
   |                 help: remove this
   |

warning: the `y:` in this pattern is redundant
  --> src/main.rs:14:23
   |
14 |         Point { x: x, y: y } => (),
   |                       --^^
   |                       |
   |                       help: remove this

non-snake-case

This lint detects variables, methods, functions, lifetime parameters and modules that don't have snake case names. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let X = 5;
#}

This will produce:

warning: variable `X` should have a snake case name such as `x`
 --> src/main.rs:2:9
  |
2 |     let X = 5;
  |         ^
  |

non-upper-case-globals

This lint detects static constants that don't have uppercase identifiers. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
static x: i32 = 5;
#}

This will produce:

warning: static variable `x` should have an upper case name such as `X`
 --> src/main.rs:1:1
  |
1 | static x: i32 = 5;
  | ^^^^^^^^^^^^^^^^^^
  |

no-mangle-generic-items

This lint detects generic items must be mangled. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
#[no_mangle]
fn foo<T>(t: T) {

}
#}

This will produce:

warning: functions generic over types must be mangled
 --> src/main.rs:2:1
  |
1 |   #[no_mangle]
  |   ------------ help: remove this attribute
2 | / fn foo<T>(t: T) {
3 | |
4 | | }
  | |_^
  |

overflowing-literals

This lint detects literal out of range for its type. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x: u8 = 1000;
#}

This will produce:

warning: literal out of range for u8
 --> src/main.rs:2:17
  |
2 |     let x: u8 = 1000;
  |                 ^^^^
  |

path-statements

This lint detects path statements with no effect. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x = 42;

x;
#}

This will produce:

warning: path statement with no effect
 --> src/main.rs:3:5
  |
3 |     x;
  |     ^^
  |

patterns-in-fns-without-body

This lint detects patterns in functions without body were that were previously erroneously allowed. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
trait Trait {
    fn foo(mut arg: u8);
}
#}

This will produce:

warning: patterns aren't allowed in methods without bodies
 --> src/main.rs:2:12
  |
2 |     fn foo(mut arg: u8);
  |            ^^^^^^^
  |
  = note: #[warn(patterns_in_fns_without_body)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #35203 <https://github.com/rust-lang/rust/issues/35203>

To fix this, remove the pattern; it can be used in the implementation without being used in the definition. That is:


# #![allow(unused_variables)]
#fn main() {
trait Trait {
    fn foo(arg: u8);
}

impl Trait for i32 {
    fn foo(mut arg: u8) {

    }
}
#}

plugin-as-library

This lint detects when compiler plugins are used as ordinary library in non-plugin crate. Some example code that triggers this lint:

#![feature(plugin)]
#![plugin(macro_crate_test)]

extern crate macro_crate_test;

private-in-public

This lint detects detect private items in public interfaces not caught by the old implementation. Some example code that triggers this lint:

pub trait Trait {
    type A;
}

pub struct S;

mod foo {
    struct Z;

    impl ::Trait for ::S {
        type A = Z;
    }
}
# fn main() {}

This will produce:

error[E0446]: private type `foo::Z` in public interface
  --> src/main.rs:11:9
   |
11 |         type A = Z;
   |         ^^^^^^^^^^^ can't leak private type

private-no-mangle-fns

This lint detects functions marked #[no_mangle] that are also private. Given that private functions aren't exposed publicly, and #[no_mangle] controls the public symbol, this combination is erroneous. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
#[no_mangle]
fn foo() {}
#}

This will produce:

warning: function is marked #[no_mangle], but not exported
 --> src/main.rs:2:1
  |
2 | fn foo() {}
  | -^^^^^^^^^^
  | |
  | help: try making it public: `pub`
  |

To fix this, either make it public or remove the #[no_mangle].

private-no-mangle-statics

This lint detects any statics marked #[no_mangle] that are private. Given that private statics aren't exposed publicly, and #[no_mangle] controls the public symbol, this combination is erroneous. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
#[no_mangle]
static X: i32 = 4;
#}

This will produce:

warning: static is marked #[no_mangle], but not exported
 --> src/main.rs:2:1
  |
2 | static X: i32 = 4;
  | -^^^^^^^^^^^^^^^^^
  | |
  | help: try making it public: `pub`
  |

To fix this, either make it public or remove the #[no_mangle].

renamed-and-removed-lints

This lint detects lints that have been renamed or removed. Some example code that triggers this lint:


# #![allow(unused_variables)]
#![deny(raw_pointer_derive)]
#fn main() {
#}

This will produce:

warning: lint raw_pointer_derive has been removed: using derive with raw pointers is ok
 --> src/main.rs:1:9
  |
1 | #![deny(raw_pointer_derive)]
  |         ^^^^^^^^^^^^^^^^^^
  |

To fix this, either remove the lint or use the new name.

safe-packed-borrows

This lint detects borrowing a field in the interior of a packed structure with alignment other than 1. Some example code that triggers this lint:

#[repr(packed)]
pub struct Unaligned<T>(pub T);

pub struct Foo {
    start: u8,
    data: Unaligned<u32>,
}

fn main() {
    let x = Foo { start: 0, data: Unaligned(1) };
    let y = &x.data.0;
}

This will produce:

warning: borrow of packed field requires unsafe function or block (error E0133)
  --> src/main.rs:11:13
   |
11 |     let y = &x.data.0;
   |             ^^^^^^^^^
   |
   = note: #[warn(safe_packed_borrows)] on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #46043 <https://github.com/rust-lang/rust/issues/46043>

stable-features

This lint detects a #[feature] attribute that's since been made stable. Some example code that triggers this lint:


# #![allow(unused_variables)]
#![feature(test_accepted_feature)]
#fn main() {
#}

This will produce:

warning: this feature has been stable since 1.0.0. Attribute no longer needed
 --> src/main.rs:1:12
  |
1 | #![feature(test_accepted_feature)]
  |            ^^^^^^^^^^^^^^^^^^^^^
  |

To fix, simply remove the #![feature] attribute, as it's no longer needed.

type-alias-bounds

This lint detects bounds in type aliases. These are not currently enforced. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
type SendVec<T: Send> = Vec<T>;
#}

This will produce:

warning: type alias is never used: `SendVec`
 --> src/main.rs:1:1
  |
1 | type SendVec<T: Send> = Vec<T>;
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |

tyvar-behind-raw-pointer

This lint detects raw pointer to an inference variable. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let data = std::ptr::null();
let _ = &data as *const *const ();

if data.is_null() {}
#}

This will produce:

warning: type annotations needed
 --> src/main.rs:4:13
  |
4 |     if data.is_null() {}
  |             ^^^^^^^
  |
  = note: #[warn(tyvar_behind_raw_pointer)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition!
  = note: for more information, see issue #46906 <https://github.com/rust-lang/rust/issues/46906>

unconditional-recursion

This lint detects functions that cannot return without calling themselves. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
fn foo() {
    foo();
}
#}

This will produce:

warning: function cannot return without recursing
 --> src/main.rs:1:1
  |
1 | fn foo() {
  | ^^^^^^^^ cannot return without recursing
2 |     foo();
  |     ----- recursive call site
  |

unions-with-drop-fields

This lint detects use of unions that contain fields with possibly non-trivial drop code. Some example code that triggers this lint:


# #![allow(unused_variables)]
#![feature(untagged_unions)]

#fn main() {
union U {
    s: String,
}
#}

This will produce:

warning: union contains a field with possibly non-trivial drop code, drop code of union fields is ignored when dropping the union
 --> src/main.rs:4:5
  |
4 |     s: String,
  |     ^^^^^^^^^
  |

unknown-lints

This lint detects unrecognized lint attribute. Some example code that triggers this lint:

#[allow(not_a_real_lint)]

This will produce:

warning: unknown lint: `not_a_real_lint`
 --> src/main.rs:1:10
  |
1 | #![allow(not_a_real_lint)]
  |          ^^^^^^^^^^^^^^^
  |

unreachable-code

This lint detects detects unreachable code paths. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
panic!("we never go past here!");

let x = 5;
#}

This will produce:

warning: unreachable statement
 --> src/main.rs:4:5
  |
4 |     let x = 5;
  |     ^^^^^^^^^^
  |

unreachable-patterns

This lint detects detects unreachable patterns. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x = 5;
match x {
    y => (),
    5 => (),
}
#}

This will produce:

warning: unreachable pattern
 --> src/main.rs:5:5
  |
5 |     5 => (),
  |     ^
  |

The y pattern will always match, so the five is impossible to reach. Remember, match arms match in order, you probably wanted to put the 5 case above the y case.

unstable-name-collision

This lint detects that you've used a name that the standard library plans to add in the future, which means that your code may fail to compile without additional type annotations in the future. Either rename, or add those annotations now.

unused-allocation

This lint detects detects unnecessary allocations that can be eliminated.

unused-assignments

This lint detects detect assignments that will never be read. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let mut x = 5;
x = 6;
#}

This will produce:

warning: value assigned to `x` is never read
 --> src/main.rs:4:5
  |
4 |     x = 6;
  |     ^
  |

unused-attributes

This lint detects detects attributes that were not used by the compiler. Some example code that triggers this lint:


# #![allow(unused_variables)]
#![feature(custom_attribute)]

#![mutable_doc]
#fn main() {
#}

This will produce:

warning: unused attribute
 --> src/main.rs:4:1
  |
4 | #![mutable_doc]
  | ^^^^^^^^^^^^^^^
  |

unused-comparisons

This lint detects comparisons made useless by limits of the types involved. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
fn foo(x: u8) {
    x >= 0;
}
#}

This will produce:

warning: comparison is useless due to type limits
 --> src/main.rs:6:5
  |
6 |     x >= 0;
  |     ^^^^^^
  |

unused-doc-comment

This lint detects detects doc comments that aren't used by rustdoc. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
/// docs for x
let x = 12;
#}

This will produce:

warning: doc comment not used by rustdoc
 --> src/main.rs:2:5
  |
2 |     /// docs for x
  |     ^^^^^^^^^^^^^^
  |

unused-features

This lint detects unused or unknown features found in crate-level #[feature] directives. To fix this, simply remove the feature flag.

unused-imports

This lint detects imports that are never used. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
use std::collections::HashMap;
#}

This will produce:

warning: unused import: `std::collections::HashMap`
 --> src/main.rs:1:5
  |
1 | use std::collections::HashMap;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |

unused-macros

This lint detects detects macros that were not used. Some example code that triggers this lint:

macro_rules! unused {
    () => {};
}

fn main() {
}

This will produce:

warning: unused macro definition
 --> src/main.rs:1:1
  |
1 | / macro_rules! unused {
2 | |     () => {};
3 | | }
  | |_^
  |

unused-must-use

This lint detects unused result of a type flagged as #[must_use]. Some example code that triggers this lint:

fn returns_result() -> Result<(), ()> {
    Ok(())
}

fn main() {
    returns_result();
}

This will produce:

warning: unused `std::result::Result` that must be used
 --> src/main.rs:6:5
  |
6 |     returns_result();
  |     ^^^^^^^^^^^^^^^^^
  |

unused-mut

This lint detects detect mut variables which don't need to be mutable. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let mut x = 5;
#}

This will produce:

warning: variable does not need to be mutable
 --> src/main.rs:2:9
  |
2 |     let mut x = 5;
  |         ----^
  |         |
  |         help: remove this `mut`
  |

unused-parens

This lint detects if, match, while and return with parentheses; they do not need them. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
if(true) {}
#}

This will produce:

warning: unnecessary parentheses around `if` condition
 --> src/main.rs:2:7
  |
2 |     if(true) {}
  |       ^^^^^^ help: remove these parentheses
  |

unused-unsafe

This lint detects unnecessary use of an unsafe block. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
unsafe {}
#}

This will produce:

warning: unnecessary `unsafe` block
 --> src/main.rs:2:5
  |
2 |     unsafe {}
  |     ^^^^^^ unnecessary `unsafe` block
  |

unused-variables

This lint detects detect variables which are not used in any way. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
let x = 5;
#}

This will produce:

warning: unused variable: `x`
 --> src/main.rs:2:9
  |
2 |     let x = 5;
  |         ^ help: consider using `_x` instead
  |

warnings

This lint is a bit special; by changing its level, you change every other warning that would produce a warning to whatever value you'd like:


# #![allow(unused_variables)]
#![deny(warnings)]
#fn main() {
#}

As such, you won't ever trigger this lint in your code directly.

while-true

This lint detects while true { }. Some example code that triggers this lint:


# #![allow(unused_variables)]
#fn main() {
while true {

}
#}

This will produce:

warning: denote infinite loops with `loop { ... }`
 --> src/main.rs:2:5
  |
2 |     while true {
  |     ^^^^^^^^^^ help: use `loop`
  |

Deny-by-default lints

These lints are all set to the 'deny' level by default.

exceeding-bitshifts

This lint detects that a shift exceeds the type's number of bits. Some example code that triggers this lint:

1_i32 << 32;

This will produce:

error: bitshift exceeds the type's number of bits
 --> src/main.rs:2:5
  |
2 |     1_i32 << 32;
  |     ^^^^^^^^^^^
  |

invalid-type-param-default

This lint detects type parameter default erroneously allowed in invalid location. Some example code that triggers this lint:

fn foo<T=i32>(t: T) {}

This will produce:

error: defaults for type parameters are only allowed in `struct`, `enum`, `type`, or `trait` definitions.
 --> src/main.rs:4:8
  |
4 | fn foo<T=i32>(t: T) {}
  |        ^
  |
  = note: #[deny(invalid_type_param_default)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #36887 <https://github.com/rust-lang/rust/issues/36887>

legacy-constructor-visibility

RFC 1506 modified some visibility rules, and changed the visibility of struct constructors. Some example code that triggers this lint:

mod m {
    pub struct S(u8);
    
    fn f() {
        // this is trying to use S from the 'use' line, but because the `u8` is
        // not pub, it is private
        ::S;
    }
}

use m::S;

This will produce:

error: private struct constructors are not usable through re-exports in outer modules
 --> src/main.rs:5:9
  |
5 |         ::S;
  |         ^^^
  |
  = note: #[deny(legacy_constructor_visibility)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #39207 <https://github.com/rust-lang/rust/issues/39207>

legacy-directory-ownership

The legacy_directory_ownership warning is issued when

  • There is a non-inline module with a #[path] attribute (e.g. #[path = "foo.rs"] mod bar;),
  • The module's file ("foo.rs" in the above example) is not named "mod.rs", and
  • The module's file contains a non-inline child module without a #[path] attribute.

The warning can be fixed by renaming the parent module to "mod.rs" and moving it into its own directory if appropriate.

missing-fragment-specifier

The missing_fragment_specifier warning is issued when an unused pattern in a macro_rules! macro definition has a meta-variable (e.g. $e) that is not followed by a fragment specifier (e.g. :expr).

This warning can always be fixed by removing the unused pattern in the macro_rules! macro definition.

mutable-transmutes

This lint catches transmuting from &T to &mut T because it is undefined behavior. Some example code that triggers this lint:

unsafe {
    let y = std::mem::transmute::<&i32, &mut i32>(&5);
}

This will produce:

error: mutating transmuted &mut T from &T may cause undefined behavior, consider instead using an UnsafeCell
 --> src/main.rs:3:17
  |
3 |         let y = std::mem::transmute::<&i32, &mut i32>(&5);
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |

no-mangle-const-items

This lint detects any const items with the #[no_mangle] attribute. Constants do not have their symbols exported, and therefore, this probably means you meant to use a static, not a const. Some example code that triggers this lint:

#[no_mangle]
const FOO: i32 = 5;

This will produce:

error: const items should never be #[no_mangle]
 --> src/main.rs:3:1
  |
3 | const FOO: i32 = 5;
  | -----^^^^^^^^^^^^^^
  | |
  | help: try a static value: `pub static`
  |

parenthesized-params-in-types-and-modules

This lint detects incorrect parentheses. Some example code that triggers this lint:

let x = 5 as usize();

This will produce:

error: parenthesized parameters may only be used with a trait
 --> src/main.rs:2:21
  |
2 |   let x = 5 as usize();
  |                     ^^
  |
  = note: #[deny(parenthesized_params_in_types_and_modules)] on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #42238 <https://github.com/rust-lang/rust/issues/42238>

To fix it, remove the ()s.

pub-use-of-private-extern-crate

This lint detects a specific situation of re-exporting a private extern crate;

safe-extern-statics

In older versions of Rust, there was a soundness issue where extern statics were allowed to be accessed in safe code. This lint now catches and denies this kind of code.

unknown-crate-types

This lint detects an unknown crate type found in a #[crate_type] directive. Some example code that triggers this lint:

#![crate_type="lol"]

This will produce:

error: invalid `crate_type` value
 --> src/lib.rs:1:1
  |
1 | #![crate_type="lol"]
  | ^^^^^^^^^^^^^^^^^^^^
  |

incoherent-fundamental-impls

This lint detects potentially-conflicting impls that were erroneously allowed. Some example code that triggers this lint:

pub trait Trait1<X> {
    type Output;
}

pub trait Trait2<X> {}

pub struct A;

impl<X, T> Trait1<X> for T where T: Trait2<X> {
    type Output = ();
}

impl<X> Trait1<Box<X>> for A {
    type Output = i32;
}

This will produce:

error: conflicting implementations of trait `Trait1<std::boxed::Box<_>>` for type `A`: (E0119)
  --> src/main.rs:13:1
   |
9  | impl<X, T> Trait1<X> for T where T: Trait2<X> {
   | --------------------------------------------- first implementation here
...
13 | impl<X> Trait1<Box<X>> for A {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `A`
   |
   = note: #[deny(incoherent_fundamental_impls)] on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #46205 <https://github.com/rust-lang/rust/issues/46205>
   = note: downstream crates may implement trait `Trait2<std::boxed::Box<_>>` for type `A`

Codegen options

All of these options are passed to rustc via the -C flag, short for "codegen." You can see a version of this list for your exact compiler by running rustc -C help.

ar

This option is deprecated and does nothing.

linker

This flag lets you control which linker rustc invokes to link your code.

link-arg=val

This flag lets you append a single extra argument to the linker invocation.

"Append" is significant; you can pass this flag multiple times to add multiple arguments.

link-args

This flag lets you append multiple extra arguments to the linker invocation. The options should be separated by spaces.

link-dead-code

Normally, the linker will remove dead code. This flag disables this behavior.

An example of when this flag might be useful is when trying to construct code coverage metrics.

lto

This flag instructs LLVM to use link time optimizations.

It takes one of two values, thin and fat. 'thin' LTO is a new feature of LLVM, 'fat' referring to the classic version of LTO.

target-cpu

This instructs rustc to generate code specifically for a particular processor.

You can run rustc --print target-cpus to see the valid options to pass here. Additionally, native can be passed to use the processor of the host machine.

target-feature

Individual targets will support different features; this flag lets you control enabling or disabling a feature.

To see the valid options and an example of use, run rustc --print target-features.

passes

This flag can be used to add extra LLVM passes to the compilation.

The list must be separated by spaces.

llvm-args

This flag can be used to pass a list of arguments directly to LLVM.

The list must be separated by spaces.

save-temps

rustc will generate temporary files during compilation; normally it will delete them after it's done with its work. This option will cause them to be preserved instead of removed.

rpath

This option allows you to set the value of rpath.

overflow-checks

This flag allows you to control the behavior of integer overflow. This flag can be passed many options:

  • To turn overflow checks on: y, yes, or on.
  • To turn overflow checks off: n, no, or off.

no-prepopulate-passes

The pass manager comes pre-populated with a list of passes; this flag ensures that list is empty.

no-vectorize-loops

By default, rustc will attempt to vectorize loops. This flag will turn that behavior off.

no-vectorize-slp

By default, rustc will attempt to vectorize loops using superword-level parallelism. This flag will turn that behavior off.

soft-float

This option will make rustc generate code using "soft floats." By default, a lot of hardware supports floating point instructions, and so the code generated will take advantage of this. "soft floats" emulate floating point instructions in software.

prefer-dynamic

By default, rustc prefers to statically link dependencies. This option will make it use dynamic linking instead.

no-integrated-as

LLVM comes with an internal assembler; this option will let you use an external assembler instead.

no-redzone

This flag allows you to disable the red zone. This flag can be passed many options:

  • To enable the red zone: y, yes, or on.
  • To disable it: n, no, or off.

relocation-model

This option lets you choose which relocation model to use.

To find the valid options for this flag, run rustc --print relocation-models.

code-model=val

This option lets you choose which code model to use.

To find the valid options for this flag, run rustc --print code-models.

metadata

This option allows you to control the metadata used for symbol mangling.

extra-filename

This option allows you to put extra data in each output filename.

codegen-units

This flag lets you control how many threads are used when doing code generation.

Increasing parallelism may speed up compile times, but may also produce slower code.

remark

This flag lets you print remarks for these optimization passes.

The list of passes should be separated by spaces.

all will remark on every pass.

no-stack-check

This option is deprecated and does nothing.

debuginfo

This flag lets you control debug information:

  • 0: no debug info at all
  • 1: line tables only
  • 2: full debug info

opt-level

This flag lets you control the optimization level.

  • 0: no optimizations
  • 1: basic optimizations
  • 2: some optimizations
  • 3: all optimizations
  • s: optimize for binary size
  • z: optimize for binary size, but also turn off loop vectorization.

debug-assertions

This flag lets you turn cfg(debug_assertions) on or off.

inline-threshold

This option lets you set the threshold for inlining a function.

The default is 225.

panic

This option lets you control what happens when the code panics.

  • abort: terminate the process upon panic
  • unwind: unwind the stack upon panic

incremental

This flag allows you to enable incremental compilation.

Targets

rustc is a cross-compiler by default. This means that you can use any compiler to build for any architecture. The list of targets are the possible architectures that you can build for.

To see all the options that you can set with a target, see the docs here.

To compile to a particular target, use the --target flag:

$ rustc src/main.rs --target=wasm32-unknown-unknown

Built-in Targets

rustc ships with the ability to compile to many targets automatically, we call these "built-in" targets, and they generally correspond to targets that the team is supporting directly.

To see the list of built-in targets, you can run rustc --print target-list, or look at the API docs. Each module there defines a builder for a particular target.

Custom Targets

If you'd like to build for a target that is not yet supported by rustc, you can use a "custom target specification" to define a target. These target specification files are JSON. To see the JSON for the host target, you can run:

$ rustc +nightly -Z unstable-options --print target-spec-json

To see it for a different target, add the --target flag:

$ rustc +nightly -Z unstable-options --target=wasm32-unknown-unknown --print target-spec-json

To use a custom target, see xargo.

Contributing to rustc

We'd love to have your help improving rustc! To that end, we've written a whole book on its internals, how it works, and how to get started working on it. To learn more, you'll want to check that out.