On Borrow Checking

Timon Gehr timon.gehr at gmx.ch
Sun May 11 12:19:16 UTC 2025


On 5/10/25 23:20, Walter Bright wrote:
> 1. `scope` is not part of the type.

I stated as much, but it still influences type checking, so it is 
formally part of the type system.

> It is a storage class, not a type 
> modifier. The only type modifiers are immutable, const, shared and 
> inout. Those are pervasively handled throughout the compiler's dealings 
> with types. Making a new pointer type would be a huge effort *in 
> addition* to all the other work needed to make a borrow checker.
> ...

Sure, so don't do it if you think it is not worth it. It's necessary to 
have annotations that are part of types to make meaningful progress on 
this though. Note that I am pretty sure that your issues with type 
modifiers in DMD are largely due to the way they are implemented. I 
don't think it would be very hard to add such features to my own frontend.

> 2. We have a lot of experience with transitive attributes. There's a lot 
> of resistance to using them because of this. Just look at all the 
> complaints about `nogc` being transitive.

Well, sometimes you want it to be transitive, sometimes you just want to 
avoid implicit allocations. It's different use cases and they should be 
distinct features. OpenD adopted my proposal for `pragma(explicit_gc)` 
and it helped me chase down some unwanted implicit GC allocations in my 
programs without flagging all the instances where I was deliberately 
using GC.

> Personally, I find `const` 
> being transitive as a serious impediment to using `const` in the dmd 
> source code. One of the big problems is it doesn't work with the Visitor 
> pattern. (There are a couple cases where `const` is just cast away, 
> shame on me.)
> ...

Well yes, as I have been saying for years, don't use `const` if you want 
to mutate. As you would not use features that restrict aliasing if you 
want to freely alias. There is no free lunch, if you want 
machine-checkable guarantees, you have to follow a certain discipline.

`@live` however is discipline for its own sake, without the slightest 
hint of practical machine-checkable guarantees.

> 3. I'm well aware that ideally @live would be sounder if it is 
> transitive.

No. Again, ideally there would be no `@live` function attribute in the 
first place. It's the wrong way to decide where to do type checks.

Anyway, there is sound and unsound. And there are directions to follow 
that can in principle lead to a sound design with enough effort, and 
there are directions to follow that will never lead to a sound design. 
`@live` is in the second category, `@safe` is in the first category.

> I disagree with the notion that it is useless if it is not.

I am not going to use it. It is useless to me. Not because it is not 
transitive, but because it does not even pretend to want to achieve 
anything.

> I am 98% sure that if @live was made transitive, it would be instantly 
> useless because nobody is going to be willing to make an existing 
> program or library pass the borrow checker.

It appears that you did not read what I wrote in my previous post, you 
are still attacking the same straw man that I already disowned.

You are correct here. `@live` is useless whether it is transitive or not.

> (It took enormous effort for 
> Rust to convince people they had to throw their existing code away and 
> recode it from scratch to use the borrow checker.) Much of the existing 
> code would have to be thrown out. Making a perfect borrow checker is the 
> enemy of making a practical, useful one.
> ...

Nonsense. Being practical and useful is of course part of perfection. 
Soundness is just also needed, if the utility sought is memory safety 
guarantees.

And if the goal is not memory safety guarantees, I just don't see why 
people would bother with this. What customer or boss will be like: "You 
really need to use `@live` when you code up this program for me." I 
really don't see it.

> 4. Making dip1000 transitive made it impractical to use in existing code.
> ...

No, the problem with DIP1000 you are referring to that it adds 
additional restrictions for existing syntax, so if you want to adopt it 
you have to first pay the up-front cost of updating your existing code. 
This simply does not happen if the new feature uses syntax that is not 
used in the wild.

Of course, even then you will get what you pay for in terms of 
annotation and type checking overhead, but with `@live` you get way less 
than what you pay.

Anyway, hardness of adoption is simply the nature of DIP1000, as its 
goal was to fix holes in @safe without making certain use cases 
impossible in @safe code outright. I suspect with editions, people will 
be able to adopt DIP1000 (or its more expressive future successor) 
incrementally, which will make it significantly more practical.

> 5. dip1000 makes it an error to assign a scope pointer to multiple 
> indirections (because scope isn't a type modifier).
> ...

Sure. Code often has multiple indirections in practice though.

> 6. When an @live function calls another function, a `scope` parameter is 
> a borrow, and a non-scope parameter is an ownership transfer.
> ...

Well, operationally it acts somewhat like these. Semantically this is 
not what happens.

As I have stated in the past, it's a bit like cargo cults in the 
pacific. Operationally, you have people that act kind of like staff 
maintaining a landing strip. In practice, no planes actually are landing 
or taking off from there.

`@live` signs contracts for borrowing and/or sale with much fanfare and 
will sometimes stop you from doing what you want to do, but no lending 
takes place and no ownership is actually transferred.

> 7. Compiling an @live function is slow because of the DFA. Making it 
> transitive would make it apply to the whole program, which would then 
> compile as slow as Rust.
> 

Not true, you only have to enhance type checking where the features are 
actually used. This is not a `@live`-specific thing at all.


More information about the Digitalmars-d mailing list