Format buffer -> error: use Rust 2018 or later

Is there a 1-liner to replace this 2-liner for constructing a string?

    let mut max_len_account: String = std::iter::repeat('t')
      .take(59) // 64 - 5 for .near
      .collect::<String>();
    max_len_account.push_str(".near");
let max_len_account: String = std::iter::repeat("t")
     .take(59) // 64 - 5 for .near
     .chain(std::iter::once(".near"))
     .collect();

Is there any reason this isn't a constant ?

1 Like

oh, duh, of course that works. thanks! In my understanding, constants are better suited for elements of a program that will be used several times in the lifetime of the code. This will be used once.

Additionally, this will be code where binary size matters, so const's are preferably avoided.

Ok, by "constant" I don't mean a literal global const. I just mean writing it out :

let name = "ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt.near";

Oh. yeah, I could do that. I wanted to allow less room for user (me) error, by specifying 59 t's.

1 Like

You could also write

let max_len_account = std::iter::repeat('t')
    .take(59) // 64 - 5 for .near
    .collect::<String>()
    + ".near";

Or if you want to avoid the use of a + operator, use core::ops::Add; and then use .add(".near").

Edit: or you could make it even simpler.

let max_len_account = "t".repeat(59) + ".near";
2 Likes

I suppose this gets at an optimization question: what's the bottleneck in the code?

I don't have strong intuitions here, but seems like a fun thing to run through a profiler.

If you want to explore the assembly produced, godbolt is very useful :

1 Like

I can kindof almost read assembly a little bit. Is there something in particular I could find useful from reading the assembly, besides the basic "this takes so many lines of assembly" that takes so many lines, etc? I didn't see a profiler attached (though I wouldn't expect one).

The number of instructions is a pretty good heuristics for performance, though there are far better people than me here for bit twiddling assembly ^^

From that godbolt it seems like a static string in memory really is smaller.

This would have the same impact on binary size as an actual const. So if they really cannot pay the price of 64 bytes, and the instructions generated by the iterator method takes up fewer than 64 bytes, they should use the iterator.

A profiler is usually used to find which part of the program is taking most of the time, so that you do not waste time optimizing one path that is called only once. If this code is called only once in your program, the profiler will show you that not much time is being spent on this code, so you don't have to care about optimization at all.

If it's in a loop that is repeated many times you can optimize it. Then you would have your alternative implementations and benchmark them. Benchmarking the different implementations would give you an indication of which implementation is actually faster for your particular inputs.

In this case, it seems to me that this code would be called very few times, so optimizing it is not going to help: I would pick the simplest most easy to understand version.

But about where the bottleneck is, I would be very surprised if the bottleneck for this code isn't the memory allocations. Basically, if an implementation reallocates more times, it's going to be slower. So I'd guess most of the time would be spend inside the reallocation function (in the assembly linked above, this is alloc:raw_vec::finish_grow which itself calls __rust_realloc).

1 Like

If you still want a String like in your OP, I'd use format over an iterator:

// "use 't' as a fill character and align right to a width of 64":
let max_len_account = format!("{:t>64}", ".near");
assert_eq!("ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt.near", max_len_account);
5 Likes