Skip to content

Trait method takes precedence over inherent method on ambiguous numeric type #99405

@steffahn

Description

@steffahn
Member

This is arguably quite confusing:

trait Trait {
    fn abs(self) -> Self;
}

impl Trait for i64 {
    fn abs(self) -> Self {
        2 * self
    }
}

fn main() {
    let x = 42;
    println!("{}", x.abs());
    println!("{}", x.abs());
}

Ouput:

84
42

So the first x.abs() call resolves to the trait method, which is then used to influence type inference to make x an i64. With x being i64, the second x.abs() call calls the inherent method.

@rustbot label T-compiler, T-lang, A-traits, A-typesystem

Activity

added
T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
T-langRelevant to the language team
on Jul 18, 2022
eddyb

eddyb commented on Jul 18, 2022

@eddyb
Member

One thing that fell through the tracks was one of my suggestions (to @workingjubilee) regarding a potential complete replacement for inherent impls on builtin types, that still has a lot of the same user-facing properties (i.e. those methods take precedence over methods defined by traits instead of being ambiguous with them).

The advantage is that if iN::abs came from a trait, we can represent <_ as core::num::IntMethods>::abs before inferring which exact type it is, whereas inherent impls require eagerly picking one.

(original motivation was making 2.0.sqrt() work, though 42.abs() is basically the same thing)

workingjubilee

workingjubilee commented on Sep 30, 2022

@workingjubilee
Member

Hello!

It didn't quite fall through the tracks so much as I wound up kinda busy at an inopportune time. I hope to take a closer look at this soon. 👀

ikey4u

ikey4u commented on Feb 23, 2024

@ikey4u

Dump the source into LLVM IR using command rustc --emit=asm,llvm-bc,llvm-ir src/main.rs, we got following code

define i32 @main(i32 %0, ptr %1) unnamed_addr #6 {
top:
  %2 = sext i32 %0 to i64
; call std::rt::lang_start
  %3 = call i64 @_ZN3std2rt10lang_start17h83abcc9980350894E(ptr @_ZN4main4main17hf586f8163becbb21E, i64 %2, ptr %1, i8 0)
  %4 = trunc i64 %3 to i32
  ret i32 %4
}

And it calls _ZN4main4main17hf586f8163becbb21E which is:

; main::main
; Function Attrs: uwtable
define internal void @_ZN4main4main17hf586f8163becbb21E() unnamed_addr #2 {
start:
  %_0.i1 = alloca { ptr, ptr }, align 8
  %_0.i = alloca { ptr, ptr }, align 8
  %_13 = alloca i64, align 8
  %_10 = alloca i64, align 8
  %_7 = alloca [2 x { ptr, ptr }], align 8
  %_3 = alloca %"core::fmt::Arguments<'_>", align 8
; call <i64 as main::Trait>::abs
  %0 = call i64 @"_ZN35_$LT$i64$u20$as$u20$main..Trait$GT$3abs17hdeedb2e9811ae8d2E"(i64 1)
  store i64 %0, ptr %_10, align 8
  store ptr %_10, ptr %_0.i1, align 8
  %1 = getelementptr inbounds { ptr, ptr }, ptr %_0.i1, i32 0, i32 1
  store ptr @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$3fmt17hd936d7e0310c6824E", ptr %1, align 8
  %2 = load ptr, ptr %_0.i1, align 8, !nonnull !4, !align !6, !noundef !4
  %3 = getelementptr inbounds { ptr, ptr }, ptr %_0.i1, i32 0, i32 1
  %4 = load ptr, ptr %3, align 8, !nonnull !4, !noundef !4
  %5 = insertvalue { ptr, ptr } poison, ptr %2, 0
  %6 = insertvalue { ptr, ptr } %5, ptr %4, 1
  %_8.0 = extractvalue { ptr, ptr } %6, 0
  %_8.1 = extractvalue { ptr, ptr } %6, 1
; call core::num::<impl i64>::abs
  %7 = call i64 @"_ZN4core3num21_$LT$impl$u20$i64$GT$3abs17h20f6cd07141369a2E"(i64 1)
  store i64 %7, ptr %_13, align 8
  store ptr %_13, ptr %_0.i, align 8
  %8 = getelementptr inbounds { ptr, ptr }, ptr %_0.i, i32 0, i32 1
  store ptr @"_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$3fmt17hd936d7e0310c6824E", ptr %8, align 8
  %9 = load ptr, ptr %_0.i, align 8, !nonnull !4, !align !6, !noundef !4
  %10 = getelementptr inbounds { ptr, ptr }, ptr %_0.i, i32 0, i32 1
  %11 = load ptr, ptr %10, align 8, !nonnull !4, !noundef !4
  %12 = insertvalue { ptr, ptr } poison, ptr %9, 0
  %13 = insertvalue { ptr, ptr } %12, ptr %11, 1
  %_11.0 = extractvalue { ptr, ptr } %13, 0
  %_11.1 = extractvalue { ptr, ptr } %13, 1
  %14 = getelementptr inbounds [2 x { ptr, ptr }], ptr %_7, i64 0, i64 0
  %15 = getelementptr inbounds { ptr, ptr }, ptr %14, i32 0, i32 0
  store ptr %_8.0, ptr %15, align 8
  %16 = getelementptr inbounds { ptr, ptr }, ptr %14, i32 0, i32 1
  store ptr %_8.1, ptr %16, align 8
  %17 = getelementptr inbounds [2 x { ptr, ptr }], ptr %_7, i64 0, i64 1
  %18 = getelementptr inbounds { ptr, ptr }, ptr %17, i32 0, i32 0
  store ptr %_11.0, ptr %18, align 8
  %19 = getelementptr inbounds { ptr, ptr }, ptr %17, i32 0, i32 1
  store ptr %_11.1, ptr %19, align 8
; call core::fmt::Arguments::new_v1
  call void @_ZN4core3fmt9Arguments6new_v117h1b56789c8e631b99E(ptr sret(%"core::fmt::Arguments<'_>") align 8 %_3, ptr align 8 @alloc_e4a2c4720f2602b7e9c0469a96b7e954, i64 3, ptr align 8 %_7, i64 2)
; call std::io::stdio::_print
  call void @_ZN3std2io5stdio6_print17hf6a70c3631f98a8bE(ptr align 8 %_3)
  ret void
}

To be simple, it does two things:

; call <i64 as main::Trait>::abs
  %0 = call i64 @"_ZN35_$LT$i64$u20$as$u20$main..Trait$GT$3abs17hdeedb2e9811ae8d2E"(i64 1)

; call core::num::<impl i64>::abs
  %7 = call i64 @"_ZN4core3num21_$LT$impl$u20$i64$GT$3abs17h20f6cd07141369a2E"(i64 1)

Other observations:

Case 1:

fn main() {
    let x = 1;
    println!("{}",` std::any::type_name_of_val(&x));
}

This snippet outputs i32.

Case 2:

fn main() {
    let x = 1;
    println!("{}", std::any::type_name_of_val(&x));
    println!("{}", x.abs());
}

This snippet does not compile:

error[E0689]: can't call method `abs` on ambiguous numeric type `{integer}`
  --> src/main.rs:16:22
   |
16 |     println!("{}", x.abs());
   |                      ^^^
   |
help: you must specify a type for this binding, like `i32`
   |
14 |     let x: i32 = 1;
   |          +++++

Case 3:

mod internal {
    pub trait Trait {
        fn abs(self) -> Self;
    }

    impl Trait for i64 {
        fn abs(self) -> Self {
            2 * self
        }
    }
}

fn main() {
    let x = 1;
    println!("{}", x.abs());

    use internal::Trait;
    println!("{}", x.abs());
}

This snippet outputs 2 1.

This behavior really surprises me, and is there any progress on this?

added a commit that references this issue on Jul 20, 2024
added
T-typesRelevant to the types team, which will review and decide on the PR/issue.
on Aug 22, 2024
added
C-discussionCategory: Discussion or questions that doesn't represent real issues.
and removed
C-bugCategory: This is a bug.
on Aug 22, 2024
jieyouxu

jieyouxu commented on Aug 22, 2024

@jieyouxu
Member

See also discussions in #121453.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-inferenceArea: Type inferenceA-trait-systemArea: Trait systemA-type-systemArea: Type systemC-discussionCategory: Discussion or questions that doesn't represent real issues.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @eddyb@steffahn@ikey4u@jieyouxu@workingjubilee

        Issue actions

          Trait method takes precedence over inherent method on ambiguous numeric type · Issue #99405 · rust-lang/rust