Skip to content

Add extern "custom"#3980

Open
folkertdev wants to merge 3 commits into
rust-lang:masterfrom
folkertdev:extern-custom-rfc
Open

Add extern "custom"#3980
folkertdev wants to merge 3 commits into
rust-lang:masterfrom
folkertdev:extern-custom-rfc

Conversation

@folkertdev

@folkertdev folkertdev commented Jul 1, 2026

Copy link
Copy Markdown

View all comments

Summary

An extern "custom" fn is a function with a custom ABI that is unknown to rust. Often these are low-level functions that pass arguments in different registers than any standard calling convention.

#[unsafe(naked)]
pub unsafe extern "custom" fn __aeabi_uidivmod() {
    core::arch::naked_asm!(
        "push {{lr}}",
        "sub sp, sp, #4",
        "mov r2, sp",
        "bl {trampoline}",
        "ldr r1, [sp]",
        "add sp, sp, #4",
        "pop {{pc}}",
        trampoline = sym crate::arm::__udivmodsi4
    );
}

unsafe extern "custom" {
	fn __fentry__();
}

History

Important

Since RFCs involve many conversations at once that can be difficult to follow, please use review comment threads on the text changes instead of direct comments on the RFC.

If you don't have a particular section of the RFC to comment on, you can click on the "Comment on this file" button on the top-right corner of the diff, to the right of the "Viewed" checkbox. This will create a separate thread even if others have commented on the file too.

Rendered

@folkertdev folkertdev changed the title add extern "custom" RFC add extern "custom" Jul 1, 2026
@folkertdev folkertdev added the I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. label Jul 1, 2026
@traviscross traviscross added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jul 1, 2026
@traviscross

Copy link
Copy Markdown
Contributor

Thanks @folkertdev for putting this together.

@rfcbot fcp merge lang

@traviscross traviscross added the P-lang-drag-1 Lang team prioritization drag level 1. label Jul 1, 2026
@rust-rfcbot

rust-rfcbot commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

@traviscross has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Jul 1, 2026
@tmandry

tmandry commented Jul 1, 2026

Copy link
Copy Markdown
Member

@rfcbot reviewed

@rust-rfcbot rust-rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Jul 1, 2026
@rust-rfcbot

Copy link
Copy Markdown
Collaborator

🔔 This is now entering its final comment period, as per the review above. 🔔

Comment thread text/0000-extern-custom.md Outdated
@traviscross traviscross changed the title add extern "custom" Add extern "custom" Jul 1, 2026
@traviscross traviscross added I-lang-radar Items that are on lang's radar and will need eventual work or consideration. and removed I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. P-lang-drag-1 Lang team prioritization drag level 1. labels Jul 1, 2026
@nikomatsakis

Copy link
Copy Markdown
Contributor

@rfcbot reviewed

@ChayimFriedman2

Copy link
Copy Markdown

Seeing a RFC entering a final comment period 1 hour after it was opened is extremely rare, and personally makes me worried. Even if it was discussed extensively in the past and all relevant team members are on-board, I believe we should let the community more time to discuss it.

Comment thread text/0000-extern-custom.md
@programmerjake

Copy link
Copy Markdown
Member

imo it's fine since that's what the final comment period is for, announcing and giving people time to look at something before it's accepted, when the team already thinks it's good enough.

@folkertdev

Copy link
Copy Markdown
Author

Right, this is the (final) ping for concerns to be raised. This was coordinated during the T-lang triage meeting.

This feature is based on the following lang proposal, it is basically unchanged from that thread.

Comment thread text/0000-extern-custom.md Outdated
Comment thread text/0000-extern-custom.md
@clarfonthey

Copy link
Copy Markdown
Contributor

I also felt a bit concerned by the sudden FCP, but the methodology here seems pretty canonical since it uses naked functions.

The only potential concern is bikeshedding the name "custom" which could potentially be worded as "none" (since this effectively is no ABI) but that's a pretty weak point and not worth blocking IMHO. Plus, it could be changed even post-acceptance but pre-stabilisation.

I see no reason to slow down on the FCP process.

@steffahn

steffahn commented Jul 2, 2026

Copy link
Copy Markdown
Member

Plus, it could be changed even post-acceptance but pre-stabilisation.

I would expect this time frame to be fairly short in this case.

@Lokathor

Lokathor commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

"none" is incorrect, as explained in the RFC, there is an ABI happening, it's just not an ABI the compiler knows how to use without assistance.

@clarfonthey

Copy link
Copy Markdown
Contributor

"none" is incorrect, as explained in the RFC, there is an ABI happening, it's just not an ABI the compiler knows how to use without assistance.

Yes, this is another reason justifying the choice, although I do think that "none" that the compiler knows about is still appropriate: the compiler is not using any ABI for the function, even though yes, technically, ABI will always exist no matter what. Even just jumping to a label is still an ABI, after all.

My point is that this is technically a weak point of contention, but not one I think is worth discussing here, since the name is more than adequately justified.

Comment on lines +111 to +118
An `extern "custom"` function cannot have any arguments or a return type:

```
error: invalid signature for `extern "custom"` function
--> <source>:6:31
|
6 | unsafe extern "custom" fn foo(a: i32) -> i32 {
| ^^^^^^ ^^^

@kennytm kennytm Jul 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the current implementation in 1.98.0-nightly (2026-07-01 4c9d2bfe4ad7a6566909) returning ! seems to be accepted (but returning an actual never_type i.e. (!) or std::convert::Infallible is rejected). please clarify

#![feature(abi_custom, never_type)]

use std::arch::naked_asm;

#[unsafe(naked)]
unsafe extern "custom" fn foo() -> ! {   // no error.
    naked_asm!("ud2")
}

#[unsafe(naked)]
unsafe extern "custom" fn bar() -> (!) { // error: invalid signature for `extern "custom"` function
    naked_asm!("ud2")
}

View changes since the review

@clarfonthey clarfonthey Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just feels like a bug IMHO, since no ABI means no way to tell if the function never returns. That said, technically, since crates using this method would benefit from knowing this, perhaps it is a reason to allow it, perhaps via some unsafe(no_return) attribute or something similar.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, is there a reason it needs to care that you wrote arguments? Since you can't call it anyway, would it make more sense to just say "sure, write whatever arguments is helpful" and rust will just not use them for anything (other than rustdoc)?

Comment on lines +81 to +94
An `extern "custom"` function definition must be unsafe. The intent here is that a safety comment is written on how this function may be called.

```
error: functions with the "custom" ABI must be unsafe
--> <source>:10:1
|
10 | extern "custom" fn bar() {
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: add the `unsafe` keyword to this definition
|
10 | unsafe extern "custom" fn bar() {
| ++++++
```

@Darksonn Darksonn Jul 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all ways of actually calling an extern "custom" function are unsafe, it doesn't seem required that we also make the functions themselves unsafe. Yes, you must call it with the right ABI for the call to be safe, but that's true for all functions. Defining such a function just seems analogous to creating a raw pointer to me.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The justification to me seems to be to encourage people to write safety comments describing the actual ABI, but I actually kind of agree and think we could have a separate clippy lint for documenting calling conventions for extern "custom" methods.

Also from the perspective of, should these function definitions be allowed with #[deny(unsafe_code)], I think the answer is yes, since only calling them is unsafe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't seem required that we also make the functions themselves unsafe.

If an extern "custom" fn can be defined as a safe function, it follows that they should also be able to be declared as safe, invalidating lines 96-109 (In an extern "custom" block, functions cannot be marked as safe).


The justification to me seems to be to encourage people to write safety comments describing the actual ABI

Maybe that mandatory #[unsafe(naked)] is enough for encouragement

should these function definitions be allowed with #[deny(unsafe_code)], I think the answer is yes, since only calling them is unsafe.

But this argument applies to defining ordinary unsafe functions too, yet this will fire the lint

#[expect(unsafe_code)]
unsafe fn just_defining_a_function_not_calling_it() {}

So unless we further refine unsafe_code into unsafe_use & unsafe_define, this should continue to be denied.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we change the unsafe requirements, lines 96-109 would also be changed. That's not a strong justification either way.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe that mandatory #[unsafe(naked)] is enough for encouragement

The unsafe on naked is there to declare that the body faithfully implements the signature. Hence standard naked functions can in fact be safe to call, despite their implementation being inherently unsafe due to the use of inline assembly.

@Darksonn Darksonn Jul 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I guess for extern "custom" functions, whether or not it's an unsafe fn is just as meaningless as which arguments appear in its arguments list. The only real difference is how people will interpret the function when they read it.

For instance, using __aeabi_uidivmod from the original motivation:

/// Multiplies two 64-bit integers.
///
/// # ABI
///
/// The first argument is passed across registers r0 (lower 32 bits) and r1
/// (upper 32 bits). The second argument spans registers r2 and r3. It returns
/// the final 64-bit result across r0 and r1.
#[naked]
pub extern "custom" fn __aeabi_lmul(multiplier: u64, multiplicand: u64) -> u64 {
    ...
}

/// Divides two 32-bit integers, computing both the quotient and remainder.
///
/// # ABI
///
/// The numerator (top number) and denominator (bottom number) are passed into
/// CPU registers r0 and r1. It returns the quotient in register r0 and the
/// remainder in register r1.
///
/// # Safety
///
/// The divisor must be non-zero.
#[naked]
pub unsafe extern "custom" fn __aeabi_uidivmod(numerator: u32, denominator: u32) -> (u32, u32) {
    ...
}

Here, we make __aeabi_lmul safe because it's safe for all inputs, and we make __aeabi_uidivmod unsafe because it's not safe for all inputs. In either case, we also document its ABI, and to actually call it with inline asm you must of course follow that ABI, but that's a separate matter from the function's own safety annotation.

Of course, neither the argument lists or whether it's marked unsafe has any real effect here except insofar it helps the reader understand the function.

Co-authored-by: Trevor Gross <tg@trevorgross.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. T-lang Relevant to the language team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.