April 09, 2020
On Wednesday, 8 April 2020 at 21:14:14 UTC, IGotD- wrote:
> On Wednesday, 8 April 2020 at 20:47:40 UTC, Steven Schveighoffer wrote:
>> [...]
>
> Just to make a counter argument against extern(C) being @system by default which is perhaps a bit counter intuitive. The programmer wants get things done fast and in practice the programmer doesn't care about if the FFI functions are @safe or @system. The programmer just want to call the FFI function the @safe function just as usual. In practice a programmer will almost every time put @safe or @trusted for every function that are imported.
>
> [...]

The code of that programmer will never pass a decent code review, plain and simple.
April 09, 2020
On 09.04.20 07:12, Steven Schveighoffer wrote:
> On 4/9/20 12:31 AM, Timon Gehr wrote:
>> On 08.04.20 22:47, Steven Schveighoffer wrote:
>>> I never said that @trusted is the same as @system.
>>
>> Nor did I claim you did.
> 
> You just said:
> 
> "If your opinion is truly that the following two code snippets are equivalent"
> 
> and then presented two code snippets that showed the same function with implementation tagged as @system or @trusted. I don't know how I'm supposed to interpret your claim other than you think I believe they are equivalent.
> ...

I showed two code snippets with the same runtime semantics and compiler diagnostics. This is precisely the condition you used to conclude that tagging extern(C) prototypes with @safe or @trusted is equivalent.

Of course I am not claiming that you think @safe and @trusted are the same. I understand that you realize that one can write code that differs only in whether an annotation is @safe or @trusted but will give different compiler diagnostics. You are clearly very familiar with the mechanics implemented in the compiler but apparently you are confused about the underlying modular verification concepts.

>> The snippets above differ only in who is to blame for the memory corruption. You claimed that's a non-essential detail, and that is not true, but I don't know how to make that point to you.
> 
> The snippets are different than what we are debating.

Not at all, but apparently I was wrong when I assumed you are aware of that fact, so I wonder why you agreed that this is a waste of time.

> We were not talking about trusted code being called from safe code, rather system code being incorrectly prototyped as @safe or @trusted. Whether you mark it incorrectly @safe or incorrectly @trusted is non-essential.
> ...

Of course it is essential that if you write only @safe code you can't get memory safety wrong. It does not matter if the @safe annotation is implicit or explicit. If you took quick a step out of the current debate, would you really consider this statement controversial?

> In both cases, the person who wrote the prototype is to blame, as the person who wrote the original code clearly meant it to be system, and the person who wrote the prototype got it wrong.
> ...

You can't blame the programmer who wrote only @safe code. It's just not an option. @safe absolves that programmer from any responsibility for memory corruption. That's what @safe is for and how it is advertised.

@system means: It's your responsibility to call this correctly.
@trusted means: I am taking responsibility for memory safety.
@safe means: The language is taking responsibility for memory safety.

The checks in @safe code are just the concrete way the language implements that responsibility. The details of how this is done in practice can evolve over time, but that fundamental spec is what it is.

> We have 3 situations:
> 
> 1. The code is written as extern(C) in D, in which case, the exact safety attribute should be repeated as the prototype.

No, in this case ideally you would just use the module system and don't write any separate prototypes.

> 2. The code is written in some other language, but is @safe based on an examination either by spec or by actually proofreading the code. In which case, the prototype could be @trusted.

Sure.

> 3. The code is written in some other language, and does not follow safety rules of D (e.g. memcpy). It should be marked @system.
> ...

Sure.

> In no circumstances should extern(C) code that is written outside D should be marked @safe.

The compiler can not check that and has to assume the worst.

> I would also consider it an error to write a prototype for an extern(C) D function other than what it actually is (@safe, @trusted, @system).
> ...

So first you say there is no difference between @safe and @trusted on prototypes, and now suddenly it is wrong to interchange the two anyway.

> I consider both of those an error on the prototype author.
> 
> I would consider it a mistake to make it impossible to forward the exact attribute of a @safe extern(C) D function to the prototype.
> 
> -Steve

As I said before, typeof(&foo) where foo is @trusted should be @safe. The difference is only important for the implementation/prototype, not the caller.
April 09, 2020
On Thursday, 9 April 2020 at 05:12:41 UTC, Steven Schveighoffer wrote:
> We have 3 situations:
>
> 1. The code is written as extern(C) in D, in which case, the exact safety attribute should be repeated as the prototype.

I don't think that should be the case. If the code is writen in D you should just import and use it. Otherwise all extern(C) should either be @trusted or @system. The extern(C) is a source for user error and the types aren't checked through mangling. So the @trusted would indicate that declaration needs to be checked. Even if the body of the function is in D and the function is defined as @safe. Since C mangling doesn't incorporate types or @safety, if you do define it as @safe in another module, then the implementation changes, it will still link to the function even if it becomes @trusted.

I think this is where the whole @system/@trusted/@system system starts to fall flat on its face, along with other situations. If you think of this as either unsafe or safe, then it is a lot easier. A extern(C) declaration without body would always be unsafe, it would be unsafe by default and there's no "safe" keyword (ala rust) to make it safe. Something that needs to default to unsafe, should stay unsafe. If there's a body of the function it would would be safe as you can verify the implementation. If you import that module, it has all that information available and it knows the declaration is correct, what types and such it uses. If you declare extern(C) elsewhere then that's the possibility for error that the declaration goes out of sync of the implementation, and C's mangling won't save you. It *needs* to be verified by an individual.


April 09, 2020
On 4/9/20 5:21 AM, Timon Gehr wrote:
> On 09.04.20 07:12, Steven Schveighoffer wrote:
>> On 4/9/20 12:31 AM, Timon Gehr wrote:
>>> On 08.04.20 22:47, Steven Schveighoffer wrote:
>>>> I never said that @trusted is the same as @system.
>>>
>>> Nor did I claim you did.
>>
>> You just said:
>>
>> "If your opinion is truly that the following two code snippets are equivalent"
>>
>> and then presented two code snippets that showed the same function with implementation tagged as @system or @trusted. I don't know how I'm supposed to interpret your claim other than you think I believe they are equivalent.
>> ...
> 
> I showed two code snippets with the same runtime semantics and compiler diagnostics. This is precisely the condition you used to conclude that tagging extern(C) prototypes with @safe or @trusted is equivalent.

No, the @system code will not prevent @safe violations, so they are not the same diagnostics.

>> The snippets are different than what we are debating.
> 
> Not at all, but apparently I was wrong when I assumed you are aware of that fact, so I wonder why you agreed that this is a waste of time.

They are different. Your example has @trusted code that is compiled by the compiler. The example we were discussing does not. It is @system code.

> 
>> We were not talking about trusted code being called from safe code, rather system code being incorrectly prototyped as @safe or @trusted. Whether you mark it incorrectly @safe or incorrectly @trusted is non-essential.
>> ...
> 
> Of course it is essential that if you write only @safe code you can't get memory safety wrong. It does not matter if the @safe annotation is implicit or explicit. If you took quick a step out of the current debate, would you really consider this statement controversial?

I now think this was NOT a waste of time! I didn't quite grasp what you are saying, though I still disagree.

I look at the attributes differently. @safe means "any code in here is mechanically checked". If I put @safe incorrectly on an extern(C) function that is really @trusted or @system, then I am wrong to do so. I don't view the compiler as having responsibility to ensure my prototypes are correctly marked (or enforce that they should be incorrectly marked, as you are suggesting).

Consider a library that is ONLY built with @safe code. It may call @trusted code from external sources, but has no @trusted markings anywhere. This includes extern(C) implementations and extern(C) prototypes for those implementations.

If I can say:

grep -R '@trusted' source

And get no results, I can be satisfied that the library is free from safety errors (obviously to a certain degree).

If I have to mark those @safe prototypes @trusted, now I get hits that are frivolous. Now, I start to ignore them because they are just frivolously required by the compiler, in actuality they are perfectly safe. So @trusted starts to lose its importance -- you now have "trusted you can ignore" and "trusted you need to check".

> 
>> In both cases, the person who wrote the prototype is to blame, as the person who wrote the original code clearly meant it to be system, and the person who wrote the prototype got it wrong.
>> ...
> 
> You can't blame the programmer who wrote only @safe code. It's just not an option. @safe absolves that programmer from any responsibility for memory corruption. That's what @safe is for and how it is advertised.

I only wrote @safe, so I am blameless:

---
pragma(mangle, "_D4core6memory2GC4freeFNaNbNiPvZv")
@safe nothrow @nogc void gcfree(void*p);

void main() @safe
{
    auto p = new int;
    gcfree(p);
    *p = 5;
}
---

This is where we differ -- @safe is to designate functions for the compiler to check and to tag prototype functions that have been checked. It only can go so far, I would not say that @safe code is undeniable *proof* that it's safe. It is a guarantee within a certain set of rules.

> 
> @system means: It's your responsibility to call this correctly.
> @trusted means: I am taking responsibility for memory safety.
> @safe means: The language is taking responsibility for memory safety.

I'm more focused on the mechanical side. @safe is an instruction to the compiler for mechanical checking, callability and linking. In practice, @safe means you can be reasonably sure that the compiler has checked such code, as long as everyone follows the rules. It's always possible to break the rules.

>> We have 3 situations:
>>
>> 1. The code is written as extern(C) in D, in which case, the exact safety attribute should be repeated as the prototype.
> 
> No, in this case ideally you would just use the module system and don't write any separate prototypes.

Ideally yes. But sometimes you need extern(C) code for other reasons. For instance, if you want code to be callable from C and D.

To clarify, I meant when specifying prototypes you have 3 situations.

>> In no circumstances should extern(C) code that is written outside D should be marked @safe.
> 
> The compiler can not check that and has to assume the worst.

Possibly there is a solution that satisfies your requirements. See my post here: https://forum.dlang.org/post/r6kvm4$1vq5$1@digitalmars.com

> 
>> I would also consider it an error to write a prototype for an extern(C) D function other than what it actually is (@safe, @trusted, @system).
>> ...
> 
> So first you say there is no difference between @safe and @trusted on prototypes, and now suddenly it is wrong to interchange the two anyway.

This is what I mean:

---
extern(C) void systemFun() @system {...}
extern(C) void safeFun() @safe {...}
---

// no measurable difference. A lie is a lie. Both are callable from the same places.
extern(C) void systemFun() @safe; // lie
extern(C) void systemFun() @trusted; // lie

// semantic difference, one is correct, one is wrong.
extern(C) void safeFun() @safe; // truth
extern(C) void safeFun() @trusted; // lie
---

-Steve
April 12, 2020
On Monday, 6 April 2020 at 12:35:20 UTC, Steven Schveighoffer wrote:
> On 4/5/20 10:43 PM, Walter Bright wrote:
>> On 4/5/2020 12:22 PM, Timon Gehr wrote:
>>> I really doubt that. It's a simple rule. The version that is easiest to implement is you simply disallow extern(C) functions without body to be marked @safe. It's a single `if` statement in an appropriate place.
>> 
>> Famous last words.
>> 
>> Just look at the swamp of misery from "simple" C rules, such as their effect on C++ overloading. The quagmire got a lot worse when C++ added type inference. I attended a Scott Meyers talk that was a full hour long just on the weird special cases forced on C++ due to those simple rules. Companies pay Scott a boatload of cash for these lectures.
>
> Is this really your argument? Do we need Scott Meyers to explain such "esoteric" compiler errors like:
>
> Error: cannot call @system function memcpy from @safe function main.
>
> I think this is not an hour-long talk, but a 10-second talk: "C functions aren't checked by the D compiler, so they are @system by default." Done.
>
> I think possibly those folks are going to be much more vulnerable to the existing rules surrounding @trusted that many of the core D developers can't seem to get right.
>
> @system by default extern(C) functions are literally the most understandable and correct part of the whole D @safe/@system/@trusted system. And you want to remove that. Please reconsider.
>

Even though the formal review period is over I'd like to leave a response to an earlier post from Walter.  I've tagged it on here since it picks up on Steve's thoughts on the topic, which I agree with, and because a direct response in the Feedback Thread was, understandably, deleted.

For context, this is an excerpt from a post that Walter made in the Feedback Thread for DIP 1028 wherein he argues *against* @system by default for extern declarations.  Interspersed are my thoughts.

[snip]
>
> 1. it's a special case inconsistency, which has its own costs and confusion.

C is not a safe language.  Defaulting C externs to @safe, as you propose, would be confusing.

>
> 2. the compiler cannot verify any extern declarations as being safe, even D
> ones. It's always going to be up to the user to annotate them correctly.

@safe should mean safe, as in machine checkable.  My position is that if it can't be machine checked, it shouldn't be considered @safe.

>
> 3. the extern(C) specifies an ABI, it doesn't say anything about how the
> function is implemented, or even which language it is implemented in. A pretty
> big chunk of the dmd implementation (80-90%?) is extern(C++)

Exactly, "it doesn't say anything..." so the default should be @system.

I'm not sure what you're trying to say in the second part. Is it that we should default to @safe because your code is safe-by-programmer-assertion and there's a lot of it?

>
> 4. it's trivial to mark a block of C function declarations with @system and
> trivial to audit it. I've done it already to a bunch of druntime headers

As you and others have noted, safety by convention doesn't scale.

>
> 5. D's separate compilation model relies on extern declarations where source is
> not available and safety cannot be machine checked. It's inherent
>

Yes.  Absent tooling upgrades, the super-safety conscious have whole program compilation and @trusted reviews to work with

> 6. We're just talking about the default. The whole point of @safe being the
> default is that it is far and away the most common case, even for C functions.

@safe doesn't work as a probability.  It aspires to certainty. ('aspires' because could be bugs in the checker, or holes in the spec or, ...)

>
> 7. A function having different attributes depending on whether or not a body is
> present is surprising behavior

Not surprising to me.  It just reflects the ability/inability of the compiler to check safety.

>
> 8. annotating "extern(C) void free(void*);" as @safe doesn't make it safe, either, again relying on the user

Relying on the user to overcome bad defaults isn't going to work.  If it did you'd not need the @safe by default DIP.

> [snip]

As many others before me, I urge you to change direction regarding extern defaults.  Barring that I hope Atila will step in for the team to prevent an own goal.





April 12, 2020
Okay, this is really a response to a post from Walter in the feedback thread, but since we're not allowed to post responses there, I'm just copy-pasting it all into the discussion thread.

On Saturday, April 11, 2020 1:30:26 AM MDT Walter Bright via Digitalmars-d wrote:
> On 3/25/2020 9:57 PM, Jonathan M Davis wrote:
> > There's also the question of declaration vs definition for functions which aren't extern(D). It makes no sense for a function declaration which is not extern(D) to be treated as @safe by default, because the compiler can't guarantee its @safety. However, I don't think that it would be a problem for non-extern(D) function definitions, because then the compiler _can_ check their @safety. Either way, the DIP should be explicit about what happens with declarations and definitions which are not extern(D) - regardless of whether declarations and definitions are treated differently.
> >
> > I'd also argue that @safe should not be allowed on function declarations which are not extern(D), because the compiler can't guarantee their @safety (meaning that if an explicit @safety attribute is supplied, it must either be @system or @trusted), and allowing @safe on non-extern(D) declarations makes it much harder for programmers to grep for @trusted code that could be the source of @safety problems. In practice, right now, I'm unaware of anyone marking extern(C) declarations as @safe, but if someone did, it could be very hard to track down the problem if it were hiding a memory safety bug, and given the ease of marking all declarations and definitions in a module with @safe via @safe: or @safe {}, it wouldn't surprise me if some code bases are accidentally marking extern(C) declarations with @safe right now and thus hiding memory safety issues. However, allowing @safe on non-extern(D) function _definitions_ should be fine, since then the compiler actually is verifying the code. Regardless, much as I think that the DIP _should_ make it illegal to mark non-extern(D) declarations as @safe, that would arguably be an improvement over what the DIP currently does rather than being required for the DIP to actually make sense or be acceptable.

> On the other hand,
>
> 1. it's a special case inconsistency, which has its own costs and confusion.

What this DIP proposes will cause even more confusion, and the "special case" here should be incredibly clear. The compiler cannot treat _anything_ as @safe unless it can mechanically verify that it's @safe, or the programmer has marked it as @trusted, indicating that _they_ have verified it. And thus, as the compiler cannot determine that a non-extern(D) declaration is memory safe, it cannot treat it as @safe. To do anything else actually makes @safe harder to understand, not easier.

It also makes it far, far more likely that extern(C) declarations will be incorrectly marked - especially since right now, leaving them unmarked results in them being correctly marked as @system, whereas after the change, properly written C bindings would suddenly be treated as @safe even though they had not been verified by the programmer to be @safe. So, this DIP _will_ introduce bugs into existing code, and it will make writing new extern(C) declarations that much more error-prone.

> 2. the compiler cannot verify any extern declarations as being safe, even D ones. It's always going to be up to the user to annotate them correctly.

Sure, but with the status quo, the compiler isn't lying about what it's verified, and you won't accidentally end up with code being treated as @safe just because an attribute was missed. @safe is about mechanical verification of memory safety, and extern(C) declarations cannot be mechanically verified, so it makes no sense for the compiler to treat them as @safe.

> 3. the extern(C) specifies an ABI, it doesn't say anything about how the
> function is implemented, or even which language it is implemented in. A
> pretty big chunk of the dmd implementation (80-90%?) is extern(C++)

Sure, and because the compiler hasn't verified it for memory safety, it's _completely_ inappropriate for it to claim that it has by treating it as @safe.

> 4. it's trivial to mark a block of C function declarations with @system and trivial to audit it. I've done it already to a bunch of druntime headers
>
> 5. D's separate compilation model relies on extern declarations where source is not available and safety cannot be machine checked. It's inherent

Sure, but extern(C) declarations aren't always in separate modules where it would make sense to just slap @system: at the top, and you're basically proposing that @trusted be reversed for extern(C) declarations. Right now, they're not assumed to be @safe unless the programmer has gone to the effort of indicating that they've verified them, whereas with the DIP, they will assumed to have been verified by the programmer unless they've been marked with @system to indicate that the programmer hasn't verified them or that they've been verified to _not_ be memory safe. That's completely backwards from how @trusted and @safety in general is supposed to work. It's also much more error-prone. You're essentially asking the programmer to explicitly mark code where there might be bugs rather than having them mark where they've verified that there aren't.

> 6. We're just talking about the default. The whole point of @safe being the default is that it is far and away the most common case, even for C functions.

Yes, and you're making the default error-prone and confusing for no benefit to the programmer. These functions have _not_ been verified by the compiler, so having the compiler mark them as @safe is a lie, and it makes the programmer's job harder. They then have to worry about tracking down extern(C) declarations that haven't been marked and whether there are bugs in the code, because a programmer failed to mark an extern(C) declaration explicitly as @system (and it could easily be that that declaration had been written prior to this DIP coming into effect, meaning that it had been correct at the time it was written but isn't anymore).

Regardless of how likely it is that a particular C function is actually memory safe, unless the compiler can verify that, having it treat it like it is just means that that function is not necessarily being checked by anyone, whereas with the status quo, the programmer will get errors if they try to use it in @safe code, forcing them to realize that it hasn't been marked as @trusted and that it either should be marked as @trusted, or it needs to be treated as @system.

It would make far, far more sense to require that all non-extern(D) declarations be explicitly marked with @system or @trusted than to have the compiler blindly treat them as @safe.

> 7. A function having different attributes depending on whether or not a body is present is surprising behavior

Is it really? It should be pretty clear that the compiler can't verify a function declaration for memory safety (even in the case of extern(D) function declarations, the compiler isn't verifying anything when compiling the declaration - it just knows that the body was checked). I would think that having the compiler treat code that it can't verify (and can't know was verified) for memory safety as @safe would be far more surprising behavior.

And if you think that having function declarations being treated differently than function definitions would be confusing, then simply treating all non-extern(D) functions as @system by default shouldn't be confusing. The rule for that is _really_ simple, and unlike what the DIP proposes, it wouldn't result in invisible problems. It's less user-friendly for anyone writing extern(C) functions in D, but at least, it wouldn't result in the compiler lying about what it's verified for memory safety, and you wouldn't even potentially be introducing confusion about declarations and definitions being treated differently.

Alternatively, you could just always require that in any cases where the compiler can't even attempt to verify code for @safety, an @safety attribute must be explicitly provided. Arguably, that's what should be happening with extern(C) declarations anyway, and it's essentially what you're claiming is best practice in the DIP. Why not just enforce that with the compiler? It would avoid having the compiler claim that code is @safe when it hasn't verified diddly-squat, and it would avoid extern(C) declarations being accidentally treated as @safe.

> 8. annotating "extern(C) void free(void*);" as @safe doesn't make it safe,
> either, again relying on the user

IMHO, marking such a function declaration with @safe shouldn't even be legal, because the compiler has not verified it for memory safety. @trusted is the appropriate attribute for that. And while the programmer could also incorrectly mark the function declaration with @trusted, at least as long as you can't mark anything with @safe which isn't mechanically verified for @safety by the compiler, you know that any memory safety bugs you run into are caused by @trusted code (or @system code that @trusted code calls) and that that's where you need to look for them. As long as non-extern(D) function declarations can be either implicitly or explicitly marked with @safe, you can't find memory safety bugs just by looking for @trusted code, and wasn't part the whole point of @trusted to make it so that you could segregate all code that hasn't actually been verified for memory safety by the compiler so that the programmer can easily find the code that could be causing memory safety bugs?

We should be making it illegal to mark anything as @safe which the compiler has not actually verified rather than making the compiler treat code that it hasn't verified as @safe just because it can't check it.

> 9. what do we do with "nothrow" by default? Say this doesn't apply to extern(C++) functions? Is anyone going to remember all these special cases?

nothrow by default would be a disaster even for extern(D) given that exceptions are by far the best way in general to deal with error conditions, and trying to disable them by default makes them much harder to use. So, I _really_ hope that you're not seriously considering making nothrow the default.

Regardless, treating non-extern(D) declarations as nothrow by default presents many of the same problems as treating them as @safe by default and makes no sense for the same reasons. The compiler can't verify that they're nothrow, so it should _not_ be treating them as nothrow. That should be simple for anyone to remember and understand. And regardless, compiler error messages should make it crystal clear if you try to call an extern(C++) function from a nothrow function, just like they should make it crystal clear if you try to call an @system function from an @safe function.

You seem to be the only person in any of the discussion for @safe by default who thinks that it makes any sense for extern(C) function declarations to be treated as @safe by default, and the only logic you seem to have to support is

1. You think that it would be a confusing special case if non-extern(D)
function declarations were treated differently from function definitions (as
would be the case if non-extern(D) function declarations were @system by
default or required explicit @safety attributes), and you think that it
would be a confusing special case to treat non-extern(D) functions
differently from extern(D) functions (as would be the case if non-extern(D)
declarations and definitions were @system by default or required explicit
@safety attributes).

2. You seem to think that just because it's ultimately up to the programmer to not make mistakes with extern(C) declarations, the compiler shouldn't help them at all (especially if that help requires introducing a special case).

It can easily be argued that treating extern(C) function declarations as @safe instead of @system is more confusing and that the fact that the compiler is claiming that code has been verified for memory safety when it hasn't been is _far_ more confusing. Regardless, I don't think that you can possibly argue that what you're proposing isn't more error-prone than the status quo. And pretty much everyone else in the discussion on this seems to think that because it's trivial for the compiler to see that an extern(C) declaration has not been verified for @safety, the compiler should use that information to help the programmer, not ignore it. Treating extern(C) declarations as @safe _will_ introduce bugs rather than catch them, and it doesn't make the programmers life any easier.

_Please_ reconsider your position on this. If this DIP goes through, then it will definitely not be the case in D that @safe code has been verified by the compiler for memory safety. It won't even be the case that it's either been verified by the compiler or the programmer. You're about to drive a truck through @safety just so that you can make @safe the default in places where in does make sense. There's no need to make it the default for non-extern(D) declarations in order to make it the default for function definitions, and you seem to be the only person here who thinks that it would make sense to do so. As this DIP stands, I pray to God that Atila rejects this DIP if you won't. And at least from the perspective of anyone actually using the compiler rather than writing it, it would be _so_ simple and straightforward to just treat extern(C) declarations as @system - and it would avoid bugs in the process at no cost to the programmer, whereas this DIP will introduce and hide bugs. And if it really is best practice to mark all non-extern(D) declarations explicitly with either @system or @trusted, then why not just require it? It would result in fewer bugs without making anything confusing.

If this DIP goes through as-is, we will be stuck explaining to D programmers for years to come why they have to be wary of the compiler treating extern(C) function declarations incorrectly and introducing invisible memory safety bugs into your program if you're not very careful. The compiler will actively be making dealing with extern(C) harder and more error-prone - and require more explanation to programmers learning D. On the other hand, if non-extern(D) declarations are @system by default or require explicit @safety attributes, then it's _simple_ to explain to people that that means that they need to be verifying the bindings for @safety and use @trusted appropriately - or that they need to just mark it them with @system and leave it up to whoever is using them. The only memory bugs introduced at that point will be because @trusted was used incorrectly rather than because an extern(C) declaration was incorrectly treated as @safe, because the programmer missed it.

- Jonathan M Davis



April 12, 2020
On Sun, Apr 12, 2020 at 05:49:34AM -0600, Jonathan M Davis via Digitalmars-d wrote: [...]
> On Saturday, April 11, 2020 1:30:26 AM MDT Walter Bright via Digitalmars-d wrote:
[...]
> > 1. it's a special case inconsistency, which has its own costs and confusion.
> 
> What this DIP proposes will cause even more confusion, and the "special case" here should be incredibly clear. The compiler cannot treat _anything_ as @safe unless it can mechanically verify that it's @safe, or the programmer has marked it as @trusted, indicating that _they_ have verified it. And thus, as the compiler cannot determine that a non-extern(D) declaration is memory safe, it cannot treat it as @safe. To do anything else actually makes @safe harder to understand, not easier.
[...]

Yes, I think a reasonable compromise is "D vs. non-D". I.e., if it's extern(D), then the D-specific defaults apply: @safe, nothrow, etc.. But if it's non-D, then pessimal defaults apply: @system, throwing, etc., and it's up to the programmer to override it if it's otherwise.

Blindly assuming @safe (and nothrow, etc.) apply to non-D declarations by default makes no sense: you can't assume D-specific things apply to any other language. It will make @safe essentially nothing more than lip service and programming by convention, and destroy whatever remnants of actual safety guarantees it may have had.


T

-- 
Bare foot: (n.) A device for locating thumb tacks on the floor.
April 12, 2020
On Sunday, 12 April 2020 at 14:25:45 UTC, H. S. Teoh wrote:
> Blindly assuming @safe (and nothrow, etc.) apply to non-D declarations by default makes no sense: you can't assume D-specific things apply to any other language.

The thing is, like Walter said, extern(language) does not mean the code was written in another language. It strictly means to use the ABI and mangling scheme of that language.
I can write my own math library in D:

```
module custommath;

float customSqrt(float x) {
    // implementation doing nothing @system
}

float customSin(float x) {
    // implementation doing nothing @system
}
```

Then I decide I want to make this a closed-source library available to C users, so I add extern(C): on top. Back on the D side, suddenly this code breaks:
```
import std;
void main() {
    writeln(customSqrt(16.0)); // error: @safe main can't call @system customSqrt
}
```

Also, I write this .di file by simply removing the function bodies:
```
module custommath;
float customSqrt(float x);
float customSin(float x);
```

Again, I suddenly can't call them from my @safe `main` function. Why did the attributes change?

I think the conflict of this discussion comes from balancing three expectations:

1. extern(language) merely specifies ABI and mangling
2. a non-auto/template function can be made extern by simply replacing the {body} into a ;
3. memory corruption can always be traced back to @system or @trusted code

Now there is actually a way to satisfy all three, and it's with @system by default :)

But with @safe by default, one of them has to go. Given that the DIP's rationale hinges on the importance of memory safety, you'd think point 3 is the most important and either point 1 is sacrificed (have only extern(D) @safe by default) or point 2 (bodyless functions must be @trusted or @system).

Surprisingly, Walter wants to keep 1. and 2. and justifies it by saying extern functions are out of scope for @safe, similar to how @safe cannot save you when bugs in the linker, operating system or processor cause memory corruption. If your bindings or the external code are faulty, you're in trouble regardless of whether you annotate it with @safe / @trusted / @system.

However, at least marking the bindings as @trusted or @system is in line with D's memory safety goal (point 3 above).
April 13, 2020
On 12.04.20 18:06, Dennis wrote:
> 
> 
> 1. extern(language) merely specifies ABI and mangling
> 2. a non-auto/template function can be made extern by simply replacing the {body} into a ;
> 3. memory corruption can always be traced back to @system or @trusted code
> 
> Now there is actually a way to satisfy all three, and it's with @system by default :)

No. 2 and 3 are mutually inconsistent.
April 14, 2020
On Sunday, 12 April 2020 at 11:49:34 UTC, Jonathan M Davis wrote:
> If this DIP goes through as-is, we will be stuck explaining to D programmers for years to come why they have to be wary of the compiler treating extern(C) function declarations incorrectly and introducing invisible memory safety bugs into your program if you're not very careful. The compiler will actively be making dealing with extern(C) harder and more error-prone - and require more explanation to programmers learning D. On the other hand, if non-extern(D) declarations are @system by default or require explicit @safety attributes, then it's _simple_ to explain to people that that means that they need to be verifying the bindings for @safety and use @trusted appropriately - or that they need to just mark it them with @system and leave it up to whoever is using them. The only memory bugs introduced at that point will be because @trusted was used incorrectly rather than because an extern(C) declaration was incorrectly treated as @safe, because the programmer missed it.

I really want to strongly back Jonathan here.

I don't usually like to pull the "as a corporate user..." line, but ... as a corporate user of D, it really matters to me that `@safe` means that memory safety checks have actually been performed on the code concerned, rather than just assumed.

All things considered it seems to me like perhaps the most straightforward way to avoid a horrible clash of concerns here is just to insist that `extern` functions must always have an _explicit_ (not inferred) memory safety attribute.

That doesn't ban putting `@safe` on them explicitly -- which might be something that could result e.g. from a `.di` file for a D lib implementing `extern(C)` code -- but it would mean that if `@safe` is on an extern function signature, it's because someone deliberately put it there.