How NLL for struct works?

Sometimes struct follows NLL rules. For example, this code is compilable (playground). In its main function:

fn main() {
    let mut i = 3;
    let foo = Foo{r: &i, v: vec![123]};
    let bar = Bar{f: foo, v: vec![222]};
    let r2 = &mut i;
    *r2 = 5;
    println!("{}", i);
}

That *r2 = 5; is legal implies the lifetime of foo ends before it, so foo follows NLL.

But this code is erroneous (playground). In it:

fn main() {
    let mut a = Vec::new();
    let mut foo = Foo::new(|v| {
        for i in v {
            a.push(i);
        }
    });
    foo.fun(1);
    println!("{:?}", a);
}

results in a compilation error:

error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
  --> src/main.rs:24:22
   |
18 |     let mut foo = Foo::new(|v| {
   |                            --- mutable borrow occurs here
19 |         for i in v {
20 |             a.push(i);
   |             - first borrow occurs due to use of `a` in closure
...
24 |     println!("{:?}", a);
   |                      ^ immutable borrow occurs here
25 | }
   | - mutable borrow might be used here, when `foo` is dropped and runs the destructor for type `Foo<'_, i32>`

which means that the lifetime of foo ends after println!("{:?}", a);, showing that foo here follows lexical rules instead of NLL.

Why is the difference, and how exactly NLL for struct works?

The difference is that the second case uses a type with a destructor (Box<T>). The destructor runs when the owner goes out of scope. (Destructors always follow simple scope-based rules; they don't change based on how a value is used.) So the borrow must live for the entire scope.

...but you can explicitly change where it goes out of scope by using drop(foo); before the println!() (for example). The difference is maintained for the sake of backwards compatibility (pre-NLL code may have relied on destructors running when they did).

But Vec implements Drop (which means it has destructor as well?). And in the first code example foo has a Vec field. So foo's lifetime should also be lexical?

The compiler can tell that the destructor of the vector cannot access the immutable reference, so it allows it.

Foo and Bar don't implement Drop though -- compiler generated "drop glue", which is not a Drop implementation, recursively drops the members. I'd link you to documentation explaining this, but as far as I know, there is no official documentation which covers all of these details.

(If you impl Drop for your structs, you will get a similar error.)

This covers what drop glue does, but I don't think it covers the difference in NLL or lexical terms.

Here's a previous forum thread on the topic as well.