Skip to content

A new lint to remove unneeded arg referencing in a format! #10851

@nyurik

Description

@nyurik
Contributor

What it does

Passing a reference to a value instead of the value itself to the format! macro causes unneeded double referencing that is not compiled away, as described in my stackoverflow question, and demonstrated in the 1.69 assembly output. The issue seem to be that reference format dispatch is not inlined by llvm. While this might be some issue with the Rust compiler itself, or an issue that is hard/impossible to solve in a general case, I think we could introduce a lint in the mean time to suggest it to improve readability and performance.

Lint Name

unneeded_format_arg_ref

Category

No response

Advantage

  • Improve runtime performance
  • Remove unneeded value referencing
  • Allow variable inlining, e.g. format!("{}", &var) --> format!("{var}")

Drawbacks

Uncertain if there could ever be an edge case where formatting &var and var would produce different result, e.g. if format wants to print the address of a variable?

Example

format("{}", &foo.bar)

Could be written as:

format("{}", foo.bar)

Activity

nyurik

nyurik commented on May 31, 2023

@nyurik
ContributorAuthor

Performance update: I consistently see a ~5% performance difference in a small benchmark.

image

disco07

disco07 commented on May 31, 2023

@disco07
Contributor

Hello for your problem, I wish to work on this subject. Is it possible? If yes, have you some suggestions to give me?

Alexendoo

Alexendoo commented on May 31, 2023

@Alexendoo
Member

For a plain binding yeah you wouldn't be able to do {:p} if you remove the & for many types. Technically it could be the case for the other formatting traits too that T has a different impl compared to &T, or no impl at all on T

Another thing to watch out for would be !Sized types e.g.

  • &v[..]
  • &*x where x derefs to something like str
nyurik

nyurik commented on May 31, 2023

@nyurik
ContributorAuthor

@Alexendoo thanks, good points. I am unclear about the last one -- all of these seems to work identically:

fn main() {
    let s = "hello world";
    println!("{}", s);
    println!("{}", &s);
    println!("{}", &*s);
}

The rest of it could be added as unit tests -- these should not trigger the lint:

fn main() {
    let vec = vec![1, 2, 3];
    let int = 42_i32;

    println!("{:?}", &vec[0..2]);
    println!("{:p}", &int);
}
nyurik

nyurik commented on May 31, 2023

@nyurik
ContributorAuthor
Alexendoo

Alexendoo commented on May 31, 2023

@Alexendoo
Member

e.g. only the last println here will compile https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ad20abfd2b21dbd4b77845b28f029d9a

struct S;

impl std::ops::Deref for S {
    type Target = str;
    fn deref(&self) -> &str {
        ""
    }
}

fn main() {
    let s = S;

    println!("{}", s);
    println!("{}", &s);
    println!("{}", *s);
    println!("{}", &*s);
}
maxammann

maxammann commented on May 31, 2023

@maxammann

Actually only the 3rd one will not compile. But same point :)

#[derive(Debug)]
struct S;

impl std::ops::Deref for S {
    type Target = str;
    fn deref(&self) -> &str {
        ""
    }
}

fn main() {
    let s = S;

    println!("{:?}", s);
    println!("{:?}", &s);
    println!("{:?}", *s);
    println!("{:?}", &*s);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2b3c6557c6d6f24c93fae05110331a7f

Alexendoo

Alexendoo commented on May 31, 2023

@Alexendoo
Member

If you change the code to something else... yes

The other two cases still change the behaviour though in that example and so shouldn't be suggested

maxammann

maxammann commented on May 31, 2023

@maxammann

Ah oke sorry, now I got your point :)

nyurik

nyurik commented on Jul 19, 2024

@nyurik
ContributorAuthor

Is there ever a case format!("{var:p}") has any meaning? Or it must always use &var? If so, it may mean that there is no code out there that compiles it, and therefor the format! macro could be improved to auto-ref on :p?

Alexendoo

Alexendoo commented on Jul 19, 2024

@Alexendoo
Member

It makes sense when var is already a reference or pointer, it would be the difference of printing the address stored in var and printing the address of var

nyurik

nyurik commented on Jul 19, 2024

@nyurik
ContributorAuthor

I somehow feel this is almost never the case... to the point of perhaps creating a lint that flags format!("{var:p}") as likely being incorrect

Alexendoo

Alexendoo commented on Jul 19, 2024

@Alexendoo
Member

When would you want to print the stack address of a pointer rather than the pointer itself?

nyurik

nyurik commented on Jul 19, 2024

@nyurik
ContributorAuthor

I don't think you ever would... thus my point that {var:p} is almost always pointless... Maybe we should submit this as a 2024 edition change - to always treat "{var:p}" as "{:p}", &var, and if someone wants to print the address of the address of the var, they will just have to write it explicitly with &&var?

Alexendoo

Alexendoo commented on Jul 19, 2024

@Alexendoo
Member

It's the other way around, &var would print the stack address

added a commit that references this issue on Jul 19, 2024
added 2 commits that reference this issue on Jul 20, 2024
added a commit that references this issue on Jul 20, 2024
added a commit that references this issue on Jul 28, 2024
added a commit that references this issue on Aug 1, 2024
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-lintArea: New lints

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @maxammann@nyurik@Alexendoo@disco07

        Issue actions

          A new lint to remove unneeded arg referencing in a format! · Issue #10851 · rust-lang/rust-clippy