Rust Launches Function Overloading Experiment to Solve C++ Interop's Last Mile

Rust's language team has launched an experimental `#[rustc_splat]` attribute in Nightly, enabling natural calling syntax for C++ overloaded functions like `hypot(2.0, 3.0, 6.0)` without tuple wrapping, as part of the Google-funded Rust-C++ Interop Initiative.

TonyBai
TonyBai
TonyBai
Rust Launches Function Overloading Experiment to Solve C++ Interop's Last Mile

On August 19, 2026, Rust core contributor teor announced on the Inside Rust blog that the function overloading language experiment has landed in the Nightly compiler. This work is driven by the Rust Foundation's Rust-C++ Interop Initiative, funded by Google, aiming to eliminate the "naming hell" when calling C++ overloaded functions from Rust.

Why Rust Has Avoided Function Overloading

In C++, std::hypot can be called with two arguments (2D distance) or three (3D distance). Rust achieves similar polymorphism via traits, but requires packing arguments into a tuple:

impl Foo<()> for MyType {
    type Return = i32;
    fn foo(&self, _args: ()) -> i32 { 42 }
}
impl Foo<(i32, i32)> for MyType {
    type Return = i32;
    fn foo(&self, args: (i32, i32)) -> i32 { args.0 + args.1 }
}

x.foo(());
x.foo((1, 2));

The extra parentheses are awkward in pure Rust but become a maintenance burden for C++ interop: every new C++ overload forces binding generators (bindgen, cxx, autocxx, Crubit) to invent a new Rust name ( func1, func2, …), turning a non-breaking C++ change into a breaking Rust change. Moreover, Rust's coherence rules reject trait implementations that could overlap in theory, even when concrete types never collide.

Existing "Pseudo-Overloading" in Stable Rust

Tuple + trait overloading: hypot((2.0, 3.0, 6.0)) — works but requires tuple wrapping.

Operator overloading: implementing Add, Neg, etc. — limited to built-in operators.

Both are insufficiently general and unnatural for FFI scenarios.

The #[rustc_splat] Experiment

To avoid premature bikeshedding, the team chose a placeholder attribute #[rustc_splat] (following the yeet keyword tradition). It lets overloaded functions be called with flat arguments:

Old (stable): hypot((2.0, 3.0, 6.0)) New (splat):

hypot(2.0, 3.0, 6.0)
splat

is purely syntactic sugar; type inference and checking remain identical to stable Rust's tuple-based overloading.

Hands-On: Calling C++ std::hypot Overloads

The official example demonstrates calling C++'s two- and three-argument hypot from Rust:

#![feature(splat, tuple_trait)]
#![expect(incomplete_features)]

use cpp::cpp;
use std::{ffi::c_double, marker::Tuple};

cpp! {{ #include <cmath> }}

/// Parameter-set trait for hypot overloads
trait HypotArgs: Tuple {
    type Output;
    fn call_hypot(self) -> Self::Output;
}

/// Splat-enabled wrapper
fn hypot<Args: HypotArgs>(
    #[rustc_splat] args: Args
) -> <Args as HypotArgs>::Output {
    args.call_hypot()
}

/// 2-argument overload
impl HypotArgs for (c_double, c_double) {
    type Output = c_double;
    fn call_hypot(self) -> c_double {
        let (x, y) = self;
        unsafe {
            cpp!([x as "double", y as "double"] -> c_double as "double" {
                return std::hypot(x, y);
            })
        }
    }
}

/// 3-argument overload
impl HypotArgs for (c_double, c_double, c_double) {
    type Output = c_double;
    fn call_hypot(self) -> c_double {
        let (x, y, z) = self;
        unsafe {
            cpp!([x as "double", y as "double", z as "double"] -> c_double as "double" {
                return std::hypot(x, y, z);
            })
        }
    }
}

fn main() {
    println!("|(3, 4)|   = {}", hypot(3.0, 4.0));
    println!("|(2, 3, 6)| = {}", hypot(2.0, 3.0, 6.0));
}

Three steps occur:

Define a HypotArgs trait (bounded by Tuple) with an associated Output and a call_hypot method.

Mark the wrapper's parameter with #[rustc_splat], telling the compiler to "unsplat" the tuple at the call site.

Implement HypotArgs for each argument tuple arity, each delegating to the corresponding C++ overload via the cpp! macro.

The caller writes hypot(3.0, 4.0) and hypot(2.0, 3.0, 6.0) naturally.

How splat Changes the Call Chain

A diagram in the article shows that splat does not alter type matching or dispatch (step 3 in both old and new flows). It only changes how arguments are passed from the call site to the trait implementation (step 2), confirming the blog's statement: "type inference and type checking remain exactly the same as stable Rust overloading."

Current Limitations

Nightly-only, no stability guarantees; may change or be removed at any time. #[rustc_splat] is a temporary syntax; final syntax is still under design.

rustdoc support merged August 12; splat parameters currently render as "…" — format unstable.

Function-pointer splat support recently merged; ICEs may occur — upgrade to latest Nightly.

Standard-library variadic smallest / greatest experiment in progress.

Issues are tracked under the F-splat label; feedback channel is Zulip #t-lang/interop.

Four Design Axioms for Rust Overloading

Keep Rust "gentle" — Overloading must be a natural extension of existing semantics, not a parallel system.

Make calling overloaded FFI functions simple — The primary practical goal of the experiment.

Maintain the foreign side's maintainability — Adding/removing a C++ overload should not cause more breakage for Rust callers than for C++ callers.

Pick the overload most developers would expect — Avoid "compiles but calls the wrong function" surprises.

The team explicitly will not replicate C++'s overload resolution rules (e.g., ADL) because different languages' rules conflict. The baseline: every C++ function must be callable, but not necessarily with identical syntax; explicit casts or disambiguation markers are acceptable.

Future Vision: #[overload] and the "Shiny Future"

The long-term sketch uses a single #[overload] attribute on an impl block, letting the compiler auto-generate the trait, tuple machinery, and #[rustc_splat]:

#[overload]
impl f64 {
    /// 2D distance to origin
    fn hypot(self, y: f64) -> f64 { … }
    /// 3D distance to origin
    fn hypot(self, y: f64, z: f64) -> f64 { … }
}

Initially, overloading would be restricted to extern blocks (interop-focused). Extending it to native Rust code is a separate, possibly never-resolved discussion. The team stresses it is too early to finalize syntax; splat 's real mission is to map the type system's boundaries, validate coverage of foreign overload patterns, improve diagnostics, and surface the hard design problems.

Resources

Runnable examples: rustfoundation/overloading-examples Ergonomics macros: rust-foundation/overloading-macros Feedback: Rust Zulip #t-lang/interop Reference: Rust Inside Rust blog "Rust Function Overloading - Call for Experimentation" (2026-08-19).

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FFIRustGooglefunction overloadingC++ interopNightlyRust Foundationsplat
TonyBai
Written by

TonyBai

Tony Bai's tech world (tonybai.com). Not satisfied with just "knowing how", we strive for mastery. Focused on Go language internals, high-quality engineering practices, and cloud‑native architecture, exploring cutting‑edge intersections of Go and AI. Gophers who pursue technology are welcome—follow me and evolve with Go.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.