tokio-rs/tokio · error

Use AioSource::register_borrowed instead

Error message

Use AioSource::register_borrowed instead

What it means

This message comes from an unimplemented!() call inside the default body of the deprecated AioSource::register method (poll_aio.rs:28-33). The trait was changed in Tokio 1.52.0 to favor register_borrowed (which takes a BorrowedFd and is I/O-safe); the old register(&mut self, kq: RawFd, token: usize) was deprecated and its default impl now panics with this message. The panic fires only if some code path actually invokes register() on an implementer that did not override it (i.e., an implementer that only implemented register_borrowed). Tokio's own MioSource wrapper (poll_aio.rs:51-79) exclusively calls register_borrowed, so internally Tokio will not trigger this; the panic is a guard against external callers or downstream crates that still call the unsafe register directly.

Source

Thrown at tokio/src/io/bsd/poll_aio.rs:32

use std::task::{ready, Context, Poll};

/// Like [`mio::event::Source`], but for POSIX AIO only.
///
/// Tokio's consumer must pass an implementer of this trait to create a
/// [`Aio`] object.  Implementers must implement at least one of [`AioSource::register`] and
/// [`AioSource::register_borrowed`].
pub trait AioSource {
    /// Registers this AIO event source with Tokio's reactor.
    ///
    /// # Safety
    ///
    /// It's memory-safe, but not I/O safe.  If the file referenced by `kq` gets dropped, then this
    /// source may end up notifying the wrong file.
    #[deprecated(since = "1.52.0", note = "use register_borrowed instead")]
    fn register(&mut self, _kq: RawFd, _token: usize) {
        // This default implementation exists so new AioSource implementers that implement the
        // register_borrowed method can compile without the need to implement register.
        unimplemented!("Use AioSource::register_borrowed instead")
    }

    /// Registers this AIO event source with Tokio's reactor.
    fn register_borrowed(&mut self, kq: BorrowedFd<'_>, token: usize) {
        // This default implementation serves to provide backwards compatibility with AioSource
        // implementers written before 1.52.0 that only implemented the unsafe `register` method.
        #[allow(deprecated)]
        self.register(kq.as_raw_fd(), token)
    }

    /// Deregisters this AIO event source with Tokio's reactor.
    fn deregister(&mut self);
}

/// Wraps the user's AioSource in order to implement mio::event::Source, which
/// is what the rest of the crate wants.
struct MioSource<T>(T);

View on GitHub (pinned to 108d6d3dc0)

Solutions

  1. Replace direct calls to source.register(raw_fd, token) with source.register_borrowed(fd.as_fd(), token); this is the supported, I/O-safe path.
  2. If you own the AioSource implementer, implement register_borrowed (and let the deprecated register stay at its default) — but make sure no code calls register() directly.
  3. If you must support old Tokio (<1.52) and new, implement both register() and register_borrowed(); in the new method delegate properly and in the old method delegate via as_fd().
  4. Audit generic helpers and trait objects for calls to register() and switch them to register_borrowed(); build with the deprecation warning enabled to catch usages.
  5. Suppress nothing — fix the call site; the deprecation on register (since="1.52.0") is the canonical signal.

Example fix

// before: calling the deprecated, unsafe method directly (panics with
// "Use AioSource::register_borrowed instead" on implementers that only
// override register_borrowed)
source.register(kq_fd.as_raw_fd(), token);

// after: use the I/O-safe borrowed registration method
source.register_borrowed(kq_fd.as_fd(), token);
Defensive patterns

Strategy: type-guard

Validate before calling

// The panic fires when tokio calls register_borrowed and your type falls
// through to the deprecated register() default -> unimplemented!().
// Surface this at TEST time, not in production:
#[test]
fn my_source_registers_safely() {
    let src = MySource::new(/* ... */);
    // This drives the registration path; it panics if register_borrowed
    // was not implemented.
    let aio = tokio::io::bsd::Aio::new_for_aio(src)
        .expect("register_borrowed must be implemented");
    let _ = aio;
}

// Also fail the build if anyone implements the old unsafe method:
// in lib.rs / main.rs:
//   #![deny(deprecated)]

Type guard

// Implement the SAFE method explicitly. Leaving register_borrowed to its
// default makes it fall through to the deprecated register() -> panic.
use std::os::fd::BorrowedFd;
use tokio::io::bsd::AioSource;

impl AioSource for MySource {
    fn register_borrowed(&mut self, kq: BorrowedFd<'_>, token: usize) {
        // real registration using the borrowed fd...
        let _ = (kq, token);
    }
    fn deregister(&mut self) {
        // ...
    }
    // Deliberately do NOT override the deprecated `register`.
}

// Call-site narrowing helper: documents that the source must be safe-API ready.
#[inline]
fn assert_safe_aio_source<S: AioSource>(_: &S) {}

Try / catch

use std::panic::{catch_unwind, AssertUnwindSafe};

// The unimplemented!() fires during Aio construction (registration). Catch it
// so a misconfigured source degrades instead of aborting the task.
let res = catch_unwind(AssertUnwindSafe(|| {
    tokio::io::bsd::Aio::new_for_aio(MySource::new(/* ... */))
}));
match res {
    Ok(Ok(aio))  => { /* usable */ }
    Ok(Err(io))  => { /* ordinary io::Error from registration */ }
    Err(payload) => {
        // register_borrowed not implemented -- fix the AioSource impl.
        eprintln!("AioSource missing register_borrowed: {payload:?}");
    }
}

Prevention

When it happens

Trigger: Triggered by calling AioSource::register(kq, token) directly on a type that implements only register_borrowed (the post-1.52.0 style), so the default register body at poll_aio.rs:32 runs unimplemented!. This happens when: (1) a downstream crate's code still calls source.register(raw_fd, token) instead of register_borrowed(fd, token); (2) a generic helper written against the old trait API invokes register() unconditionally; (3) a trait object or generic that calls register() reaches the default impl because the implementer relies on the new method. Note: an implementer that overrides ONLY the old register() will still work via register_borrowed's backwards-compat default (poll_aio.rs:36-41), so the panic specifically implies the implementer overrode register_borrowed but not register, AND register() was called.

Common situations: Crate upgrade to Tokio >= 1.52.0 where a custom POSIX AIO source was migrated to register_borrowed but a separate helper still calls the old register(); copy-pasted code from pre-1.52 examples; trait objects dispatching to register() at runtime; FreeBSD/macOS AIO integrations (kqueue-based) that bypass Tokio's MioSource and call into the source directly; Cargo.toml version drift where the implementer and caller are in different crates updated independently.

Related errors


AI-assisted analysis of tokio-rs/tokio@108d6d3dc0 (2026-08-01). Data as JSON: /data/errors/0d65bce651f37765.json. Report an issue: GitHub.