Confused about Box::new function implementation

pub struct Box<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);

impl<T> Box<T> {
    /// Allocates memory on the heap and then places `x` into it.
    ///
    /// This doesn't actually allocate if `T` is zero-sized.
    ///
    /// # Examples
    ///
    /// ```
    /// let five = Box::new(5);
    /// ```
    #[cfg(all(not(no_global_oom_handling)))]
    #[inline(always)]
    #[stable(feature = "rust1", since = "1.0.0")]
    #[must_use]
    pub fn new(x: T) -> Self {
        #[rustc_box]
        Box::new(x)      // confused at this line.     
    }

when call Box::new(x), why return the recursive call self function?

The #[rustc_box] attribute gives that special compiler behavior -- it used to be an actual box keyword!

4 Likes

It's similar to how the Add trait is implemented like this for integers:

fn add(self, other: i32) -> i32 { self + other }

The + symbol for i32 is a compiler intrinsic, and unlike for custom types, it doesn't just call the Add trait.

Has the box keyword been removed? I saw #49733 and #97293, but I don't really understand them. Just curious about whether box is going to be used somewhere or if it will disappear. (And I also don't understand the performance discussion in #97293. Is that more than just a syntax change?)

Well, the keyword is still used in a few places in the std crate. I haven't been directly involved in that feature, so I can't tell you any more than what you see in the places you linked.

box $expr is special because it has different semantics from Box::new($expr). Roughly,

// box $expr
let p = alloc(Layout::new::<typeof!($expr)>());
p.write($expr);
p

// Box::new($expr)
let tmp = $expr;
let p = alloc(Layout::new::<typeof!(tmp)>());
p.write(tmp);
p

This matters because of the order of the alloc and evaluating $expr; it's significantly easier for LLVM to convert the former to do move elision of tmp from the latter.

But perhaps more relevant to that PR is compiler performance (rather than performance of the built binary). Introducing #[rustc_box] requires an extra step from the compiler to recognize the attribute (potentially going through name resolution). The more the compiler needs to do the longer it'll take to do it.

Plus, compile perf is fundamentally opaque due to a lot of factors, with a big one being that small source changes can result in LLVM making drastically different optimization choices, and the impact of this is magnified for fundamental generic types due to them being monomorphized into and recompiled as part of every crate.

4 Likes

Which is very relevant when $expr is several megabytes, enough to blow the stack!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.