Embedded: rust compiler doesn't create interrupt handler


I'm compiling some rust code for the avr attiny85 and have a big problem: it seems that code for interrupt handlers is not created! I've tried to create a minimal program to reproduce this:

#![allow(non_snake_case)] // for ISRs

use attiny_hal::{clock, delay, prelude::*};
use avr_device;
use avr_device::interrupt;

static mut A: u8 = 7;

pub extern "C" fn main() {
    let mut delay = delay::Delay::<clock::MHz1>::new();

    loop {

fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}

// __vector_15
fn USI_OVF() {
    unsafe {
        A += 1;

// __vector_3
fn PCINT0() {
    unsafe {
        A += 1;

According to the attiny documentation, USI_OVF is vector 15, but using nm, that is not generated at all:

00000054 W __vector_1
00000054 W __vector_10
00000054 W __vector_11
00000054 W __vector_12
00000054 W __vector_13
00000086 T __vector_14
00000086 T __vector_2
00000054 W __vector_3
00000054 W __vector_4
00000054 W __vector_5
00000054 W __vector_6
00000054 W __vector_7
00000054 W __vector_8
00000054 W __vector_9
00000000 W __vector_default

That result is completely unexpected for me. No __vector_15 at all, __vector_3 (i.e. PCINT0) is there, but doesn't use my code. Therefore, vectors 2 and 14 have an implementation (the same?)...

Do I have something completely misconfigured?

That's the cargo.toml:

name = "rust-attiny85-missing-interrupt"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

panic = "abort"
lto = true
opt-level = "s"
debug-assertions = false

panic = "abort"
lto = false
codegen-units = 1
opt-level = "s"
debug-assertions = true
strip = false

avr-device = "0.5.1"

git = "https://github.com/rahix/avr-hal"
version = "0.1.0"
features = ["attiny85", "rt"]


target = "avr-attiny85.json"

build-std = ["core", "panic_abort"]
build-std-features = [ "panic_immediate_abort" ]


  "arch": "avr",
  "atomic-cas": false,
  "cpu": "attiny85",
  "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
  "eh-frame-header": false,
  "exe-suffix": ".elf",
  "executables": true,
  "late-link-args": {
    "gcc": [
  "linker": "avr-gcc",
  "llvm-target": "avr-unknown-unknown",
  "max-atomic-width": 8,
  "no-default-libraries": false,
  "pre-link-args": {
    "gcc": [
  "target-c-int-width": "16",
  "target-pointer-width": "16"

I completely don't understand this result and hope someone can help me...


What happens if you declare your interrupt functions as public (pub fn instead of fn)?

Maybe compiler observes that they are not used (because nobody calls them) and just is not generating them (so linker couldn't use them either).

Interesting idea... doesn't work, unfortunately:

error: `#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`

Explicitly adding unsafe doesn't help, neither.

According to Interrupt in avr_device::attiny85 - Rust, for the attiny85, PCINT0 is __vector_2, and USI_OVF is __vector_14. When I look at the source code for the #[interrupt] macro, it's the values in this file which correspond to the ones in attiny85::Interrupt that are used by the macro.

This is backed up when I look at the ATtiny85 datasheet; interrupt 3 is at address 2, and interrupt 15 is at address 14; I would guess that the crates you're using name the interrupt vectors after their locations in memory, not their interrupt number.

Is your code in place for __vector_2 for PCINT0, and __vector_14 for USI_OVF? Have you looked at the interrupt table as assembled to see if it contains an rjmp at the appropriate locations as per the datasheet recommendation?

Ah, now it makes sense. Thank you!