July 13
On 7/13/2024 12:02 PM, Dennis wrote:
> I need a link to that proposal and the rejections.

Let's take this to d.d.ideas
July 13
On 7/13/24 21:27, Walter Bright wrote:
> On 7/13/2024 6:09 AM, Timon Gehr wrote:
>>> This to me is the most sensible way to go about it.
>>
>> It's not. You'll quickly lose track of the 3 kinds of `@trusted` functions:
>>
>> - @trusted functions that should be @safe
>> - @trusted functions that should be @trusted
>> - @trusted functions that must be @system
>>
>> This is plain safewashing.
> 
> Not if the @trusted functions have a safe interface (though the compiler cannot check the interface for safety, it is up to the programmer).
> ...

Well, your suggestion was to put @trusted _everywhere_, not only on functions with a safe interface. As Dennis points out, it seems that is exactly what happened in some instances.

Anyway, `@trusted` indicates that whoever put it there reviewed the implementation for memory safety. This makes this approach impractical.

> 
>> If you want incremental safety in the current language, you have to start at the leaves, and put your TODO's in comments.
> 
> In my efforts to make the front end @safe, that approach does not work. The problem is that the front end flow graph is cyclical. There aren't many leaves, and a lot of those leaves in the front end have been marked as @safe.
> ...

The solution is to remove the unsafe constructs and then mark everything safe in one go. The compiler currently cannot assist with this very well, but it is easy to do. It should just have a way to enable non-transitive safety checks.

> 
>> OpenD has an interesting approach towards default safety. They enabled a large fraction of safety checks in functions without a safety annotation. To disable them, explicit @system is required. This way you get linting benefits even without already ensuring full memory safety guarantees.
> 
> It's an interesting approach, but certainly half-baked. How does one decide which safety checks are to be included?
> ...

In a data-driven way. They enabled the checks that do not cause too much pain to users. OpenD is a big monorepository with DMD, LDC, and most of the packages that people actually use, so breaking changes actually require breakage to be fixed as well.

Anyway, non-transitive checks can be a useful tool for all function attributes, and they have the benefit of not over-specifying their guarantees. This can even be done with pragmas. I implemented a simple pragma to (non-transitively) error out on implicit GC allocations, and it was helpful for performance optimization.

> 
>> Anyway, there are many language-assisted ways to make incremental @safe migration work (some would require only minimal changes to the compiler), but I think slapping @trusted everywhere will not do anything to improve the perceptions about D's memory safety.
> 
> You've often been quite adamant about memory safety being 100% (and I tend to appreciate that point of view and am in agreement with it).

Yes, @safe should mean memory safe. It should not mean: We'd like this to be memory safe, but it is work in progress.

> A half-baked approach won't help, either.
> ...

Well, if the goal is incremental migration to `@safe`, a process of enabling non-transitive safety checks on a function-by-function basis and then later making it transitive is much more baked than random application of @trusted, and it completely bypasses any issues with big cyclic call graphs.

> With the @trusted approach, I'll often change it to @safe, fix what I can, then put it back to @trusted.

The fake-`@trusted` approach is half-baked, error prone, and undercuts @safe. The fully baked way is to provide actual tooling for the use case of incremental migration to memory safety.


July 14
Dennis kirjoitti 13.7.2024 klo 20.16:
> On Thursday, 11 July 2024 at 16:26:59 UTC, Walter Bright wrote:
>> The way to do it incrementally is to start by labeling each function @trusted, then making them @safe one by one.
>>
>> I've done this, it works.
> 
> I know you've done the first part, because there's now 1122 `@trusted` functions in  dmd.backend. Many of them have an unsafe interface such as:
> 
> ```D
> @trusted
> void list_delete(list_t list) { free(list); }
> ```
> 
> Not much actual `@safe` code has come from it unfortunately.
> 

Why in the world Walter did that?

He has complained that lambdas like `(@trusted { list.list_delete(); })()` are a bad practice, since they hide the fact a `@safe` function is actually calling unsafe functions. But isn't this much worse? Not only are `@safe` functions calling unsafe functions, there isn't even a `@trusted` in the function body to warn about the fact!

I'd have expected Walter to mark the calling functions `@trusted` instead, if he feels the lambda trick is too dangerous.
July 15
On 7/11/2024 3:18 AM, Timon Gehr wrote:
> If even the main language designer advocates for randomly sprinkling `@trusted` to shut up the compiler, there is no hope for memory safe D.

The only way to do a conversion of a large program is to mark them all @trusted and then fix the functions one by one. The remaining @trusted annotations are the TODO list.

If there's another practical way, I'm not aware of it.
July 15
On 7/11/2024 3:20 AM, Timon Gehr wrote:
> The simple point remains that Steven is absolutely correct that putting `@safe:` does not work.

True, when the code is not @safe and needs fixin'.
July 15
Those are good suggestions, but I've been trying to make dmd @safe now for a while. With all the inheritance going on, and the flow graph cycles, your suggestions are not workable.

For programs that have an acyclical graph, and little inheritance, your suggestions are more tractable.

Just making `toChars()` safe has turnout to be a major undertaking.
July 17
Walter Bright kirjoitti 15.7.2024 klo 21.20:
> On 7/11/2024 3:18 AM, Timon Gehr wrote:
>> If even the main language designer advocates for randomly sprinkling `@trusted` to shut up the compiler, there is no hope for memory safe D.
> 
> The only way to do a conversion of a large program is to mark them all @trusted and then fix the functions one by one. The remaining @trusted annotations are the TODO list.
> 
> If there's another practical way, I'm not aware of it.

1. Start by doing just what you did: mark something `@safe` that should be `@safe` and see what error messages pop up.

2. When the compiler complains you're calling a `@system` function, stop and think. If the function you're trying to call is supposed to have a safe API, then fine, go and mark it as `@trusted` and add it to todo list. But if you are, for instance, calling anything that takes a C string as an argument, please don't schlepp `@trusted` on that - that would be marking an unsafe API @safe. Use the trusted lambda trick instead.

3. If you had to add trusted lambdas and it annoys you, remove them when you're done and instead mark the whole function you were working on as `@trusted`.
July 17
On Monday, July 15, 2024 12:21:24 PM MDT Walter Bright via Digitalmars-d wrote:
> On 7/11/2024 3:20 AM, Timon Gehr wrote:
> > The simple point remains that Steven is absolutely correct that putting `@safe:` does not work.
>
> True, when the code is not @safe and needs fixin'.

It is the case more frequently than not that you do not want to mark templated functions with @safe, and there is nothing about that that needs fixing.

Most templated functions should be designed in a way that they work with either @safe or @system depending on the template arguments, because you're unnecessarily restricting the types or functions that can be used with the template otherwise. Ideally, the templated function will be written in a way that it will be @safe if all of the functions it's calling are @safe but not require that any of them be @safe. Range-based code is a prime example of this, because some ranges are @safe and some aren't, but code like std.algorithm needs to work with them either way. If that templated code is written properly, it will then be @safe if the range can be @safe, but if it actually required @safe, then it wouldn't work with ranges that weren't @safe. As such, for the majority of templated code, using @safe: is a disaster and does not indicate that _anything_ needs fixing. Inference is what's needed for the vast majority of templates.

Now, if you're writing a template that works on a specific subset of known types (e.g. all integer types), then it becomes possible to use @safe explicitly, because you know that what you're doing can be @safe for all of the types that the templated function can be instantiated with, and it could be considered a bug if the code didn't work with @safe:, but for many templates, you need @safe to be inferred, and using @safe: causes nothing but trouble.

In most cases, slapping : at the top of a module with any attribute causes issues. It can even cause problems with stuff like public, since that can easily result in a situation where you accidentally make imports public. In general, : should be used with great caution with regards to attributes, and in many cases, IMHO, it borders on being a code smell given how error-prone it can be. It also becomes a problem with maintenance and code reviews, because it's often not obvious that a particular attribute is in effect in a section of the file when it was applied using :. So, using @safe: really is not a solution for much of anything. It _can_ work in some cases, but I'd strongly advise against it in general, because it will cause problems.

- Jonathan M Davis



July 17
On 7/15/24 20:24, Walter Bright wrote:
> Those are good suggestions, but I've been trying to make dmd @safe now for a while. With all the inheritance going on, and the flow graph cycles, your suggestions are not workable.
> 

What I suggested is in fact workable even for code with a big cyclical call graph.

July 19

On Saturday, 6 July 2024 at 23:39:54 UTC, Sebastian Nibisz wrote:

>

On Saturday, 6 July 2024 at 23:10:02 UTC, Walter Bright wrote:

>

On 7/6/2024 4:07 AM, Sebastian Nibisz wrote:

>

Seriously? Any language is safe in this case, you just need to write safe code.

Enabling the checks is quite different from writing code with no bugs in it.

But you have to remember to enable it. Inexperienced programmer usually won't do this and will build unsafe code unconsciously.

This is the single best reason to enable @safe by default. Writing correct @system code is as hard as writing @trusted code, and both require the programmer to know the language very, very well. You shouldn’t mark a function @system or @trusted unless you understand exactly why that’s the right thing.


The only big issue with @safe by default is higher-order functions. Because @safe makes no guarantees (unlike pure or nothrow), requiring a callback delegate to be @safe makes no sense generally. (In contrast, requiring a pure or nothrow callback can make sense in special circumstances. Practically, if your higher-order function can be called with a @safe callback, it can be called with a @system callback. The problem is that the language does not understand this.

This means, in general, higher-order functions must be overloaded:

void hof(void delegate() @system callback) @system => hof(cast(void delegate() @safe)callback);

void hof(void delegate() @safe callback) @safe
{
    callback();
}

For application code, it might be fine if a callback is needlessly required to be @safe if the application is @safe code anyway. Libraries can’t make such assumptions on usage, though.

Top-level @safe: does not influence callback types. In a DIP Idea, I proposed default @safe module declarations, so that for any declaration lexically in the module, @safe is applied by default instead of @system.