Anyway to shorten `.into()` to `.i`?

I do want there to be some indication that there is an Into conversion going on, but I would prefer to not spend 7 width characters on it. By declaring MyInto, we can shorten it to .i(), but is there anyway to get it down to .i ?

I can think of deref: that only require &

or overriding something like std::ops::neg: that only require !

Even macros can't do better xD

1 Like

Personally I would rather see .into()' than .i()orI`.

Sure it takes a few milliseconds longer to type it but at least when I read it I don't have to wonder what on earth you are up to. I doubt reading it would be slower, I suspect it would be quicker due to the absence of weirdness. Don't forget, code is read more often than it is written.

Whilst I don't like to see verbosity I feel excess brevity gets in the way as well.

10 Likes

I completely agree.

However, it's not typing speed that bothers me, it's vertical space.

The way auto line-breaking is working in Rust for me is that when lines get long enough, it gets broken at every .blah(), so we have lines lke:

let x = blahblalh
  .iter()
  .foo()
  .bar()
  .baz();

Ah ha, OK.

Somehow I don't run into that problem much.

But then I'm kind of allergic to the .iter().this().that().theOther().shootICantSeeWhatIsGoingOnAnymore(); style.

Also, I don't worry about the verticle space much. I like to see my programs laid out in a line per step style. Probably because I started out in programming life working in assembler.

2 Likes

This is probably an agree to disagree thing, I often write:

let descriptive_name: TypeSig = dont.care().how().this().works().since().right().type().means().probably().right().code();

and go from there, where the descriptive name and TypeSig both eat non-trivial width, which then pushes the rhs into separate lihnes.

I can do that :slight_smile:

If I have some().complicated().thing().that().I().dont().care().how().it().works() I often find myself factoring it out into into a function or method with a descriptive name. Then when reading the main code I have a clue what it is doing for me and don't have to worry about how it works.

Which conveniently reduces the line count of the to level code making it easier to read.

2 Likes

Syntactically, expr.i means you are accessing the i field inside the value some expression evaluates to.

Unless your type actually contains a i field (which it won't because that's why we need to invoke a conversion in the first place), this probably isn't going to be possible.

Personally, I don't see an issue with writing into(). Your brain will automatically recognise the pattern and skim over it while reading, and when typing you often don't have to type much more than .i before autocomplete finishes it for you anyway.

4 Likes

Oddly enough that is something that has been puzzling me for three decades or more.

As an example, imagine I want to get an area of some shape I have to hand. I might end up having to write something like:

let area = shape.area;

But later I might like that my shape does not have an area field but rather it is something that needs to be calculated when requested. That is I want an area() method. That now means I have to change all the places in my code that want an area to look like:

let area =shape.area();

Which is a pain.

So my thought, all those years ago, was why not just allow shape.area and have the compiler generate code for a method call or whatever field access it requires appropriately. Then we could swap implementations from field to method or vice versa without impacting the caller code. After all, the caller does not care what it is.

I see this as a case where the mechanics of the implementation has leaked through the high level abstraction of the language.

With borrowck that's not quite true, though. Because a method call borrows the whole thing, while just looking at the field borrows only the field, which makes a big difference if you want to look at multiple at once.

4 Likes

This is exactly what Pascal and Delphi do. Ruby also lets you drop the () when invoking a method with no parameters.

1 Like

Good point.

Isn't that "borrows only the field" a new thing in the 2021 edition?

I guess then that changing my .area field into a .area() method will change the whole borrowing situation and likely make my whole higher level design untenable.

Nope. It's been that way for ages. Here's the nomicon from back in 1.10: https://doc.rust-lang.org/1.10.0/nomicon/borrow-splitting.html

Although that doesn't work in a language that relies on higher-order functions so much. If mentioning the name of a 0-ary function automatically calls it, then there is no way to pass the function itself to a higher-order function.

2 Likes

I just created an example shortening it to .i(), when I re-read your question asking to shorten it to i (without parenthesis). Anyway, maybe other readers are interested in how it works with .i(), so I'm sharing my already made up example code:

mod short_into {
    pub trait IntoExt<T>: Sized
    where
        Self: Into<T>,
    {
        fn i(self) -> T {
            self.into()
        }
    }
    impl<T, U: Into<T>> IntoExt<T> for U {}
}

use short_into::IntoExt;

fn main() {
    const N: i16 = 5;
    let n: i32 = N.i();
    println!("Hello {}", n);
}

(Playground)

Output:

Hello 5

I assume it's technically possible to shorten it to i with a function-like procedural macro. Something like:

into_as_i!({
    const N: i16 = 5;
    let n i32 = N.i;
    n
})

But not sure how such a macro could be defined, but I think it's possible, isn't it?

P.S.: I wouldn't want to use that syntax myself, just asking out of curiosity.

Yeah, upon rethinking this, .i , without the (), does seem a bit unreasonable.

So the job to be done in that case would be to replace things in the AST that look like field accesses with the name i with calls to into.

The syn crate has an example of how to replace parts of the AST. For this macro, instead of matching on Expr::Lit you would match on Expr::Field, and only do the replacement if the contained ExprField's Member field contains an Ident that returns "i" from its to_string method.

So it's fairly straightforward to define this macro. Whether it would be a good idea to use the macro is a separate question, of course.

1 Like

I believe you're thinking about closures doing partial (field) captures.

1 Like

Ah yes.

Macro could do better.

I tried to implement CustomInitialize and CustomAssign trait in both my toy crate and this forum, which allow using

fn main() {cai!{// Custom Assign and Initialize, or "菜" in Chinese which suggest my programming skill is not good enough.
    let a:i32:=1u8;
    let mut b=1i32;
    b~a;
    b:=a;
}}

by define the corresponding trait correctly.
no need to write Neg trait which actually do not support convert the base type to different types.