Thread overview | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
January 08, 2020 @disable("reason") | ||||
---|---|---|---|---|
| ||||
Hello! I'm writing a library where under certain conditions i need all the default constructors to be disabled. I would like to tell the user why they can't instantiate the struct. Is there a way to do that? |
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Marcel | On Tuesday, January 7, 2020 5:23:48 PM MST Marcel via Digitalmars-d-learn wrote:
> Hello!
> I'm writing a library where under certain conditions i need all
> the default constructors to be disabled. I would like to tell the
> user why they can't instantiate the struct.
> Is there a way to do that?
In terms of an error message? Not really. You can put a pragma(msg, ""); in there, but that would always print, not just when someone tried to use it. I'd suggest that you just put the information in the documentation. If they write code that doesn't work because of you using
@disable this();
the documentation is where people will look to find out why anyway.
And BTW, structs don't have default constructors in D. So, maybe it's just a question of terminology rather than you misunderstanding the semantics, but all variables in D get default initialized with their type's init value, including structs, and you can't actually declare a default constructor for a struct. So, if you have
@disable this();
in a struct, all that that's doing is disabling default initialization, not default construction. So, code like
MyStruct ms;
or
MyStruct[] arr;
won't compile, because they require default initialization. But even then, you don't actually get rid of the default value of the struct. You just make it so that it doesn't get used implicitly. Code such as
auto ms = MyStruct.init;
or
MyStruct ms = MyStruct.init;
will still work. So, you still potentially have to worry about the type's init value being used (though you could just document that no one should ever use its init value explicitly, and that they will have bugs if they do, not that that actually prevents people from using it incorrectly; it just informs them that they shouldn't). It's unlikely that many people will try to use the init value explicitly, but some generic code may do so (e.g. IIRC, std.algorithm's move function uses it on the source variable after moving the value to the target variable).
- Jonathan M Davis
|
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Marcel | On Wednesday, 8 January 2020 at 00:23:48 UTC, Marcel wrote: > Hello! > I'm writing a library where under certain conditions i need all the default constructors to be disabled. I would like to tell the user why they can't instantiate the struct. > Is there a way to do that? class Example { @disable this() { pragma(msg, "not allowed..."); } } void main() { new Example(); } outputs: > not allowed... > /tmp/temp_7F8C65489550.d(12,5): Error: constructor `runnable.Example.this` cannot be used because it is annotated with `@disable` Because pragma(msg) are evaluated when encountered you can use abuse them. By specification they're not allowed to modify the meaning of the program. |
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to user1234 | On Wednesday, 8 January 2020 at 08:26:51 UTC, user1234 wrote:
> class Example
> {
> @disable this() { pragma(msg, "not allowed..."); }
> }
>
> void main()
> {
> new Example();
> }
>
> outputs:
>
>> not allowed...
>> /tmp/temp_7F8C65489550.d(12,5): Error: constructor `runnable.Example.this` cannot be used because it is annotated with `@disable`
However, it will print that message even if the constructor is never called. If you make the constructor a template instead, you will only get the message when someone attempts to use the default constructor:
class Example
{
@disable this()() { pragma(msg, "not allowed..."); }
}
void main()
{
new Example();
}
Sadly, this does not work for structs, as they don't really have a default constructor, as Jonathan pointed out.
--
Simen
|
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Wednesday, 8 January 2020 at 07:03:26 UTC, Jonathan M Davis wrote:
> you could just document that no one should ever use its init
> value explicitly, and that they will have bugs if they do
You also create a static init member marked @disable:
struct S {
@disable this();
@disable static S init();
}
This will give sensible error messages anywhere .init is being used. Now, Phobos and other libraries might expect that .init is always working, so this could potentially be a problem.
|
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Simen Kjærås | On Wednesday, January 8, 2020 4:54:06 AM MST Simen Kjærås via Digitalmars-d- learn wrote:
> On Wednesday, 8 January 2020 at 07:03:26 UTC, Jonathan M Davis
>
> wrote:
> > you could just document that no one should ever use its init value explicitly, and that they will have bugs if they do
>
> You also create a static init member marked @disable:
>
> struct S {
> @disable this();
> @disable static S init();
> }
>
> This will give sensible error messages anywhere .init is being used. Now, Phobos and other libraries might expect that .init is always working, so this could potentially be a problem.
That's likely to break a _lot_ of generic code, because init is used heavily in template constraints and static if conditions as the way to get a value of that type to test. It's also actually been argued before that it should be illegal to declare a symbol called init on any type, which is one reason why the init member was removed from TypeInfo. I'd strongly advise against anyone declaring a struct or class member named init whether it's @disabled or not.
- Jonathan M Davis
|
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Marcel | On Wednesday, 8 January 2020 at 00:23:48 UTC, Marcel wrote:
> I would like to tell the user why they can't instantiate the struct.
> Is there a way to do that?
I'd love to have exactly what you said for this reason, but D doesn't really have it. You just have to hope they read the docs (my doc generator specifically calls out default ctors for this reason).
But we should formally request @disable("reason") to be added, it really is very nice and has precedent in deprecated("reason").
|
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
On Wed, Jan 08, 2020 at 06:06:33AM -0700, Jonathan M Davis via Digitalmars-d-learn wrote: > On Wednesday, January 8, 2020 4:54:06 AM MST Simen Kjærås via Digitalmars-d- learn wrote: [...] > > struct S { > > @disable this(); > > @disable static S init(); > > } > > > > This will give sensible error messages anywhere .init is being used. Now, Phobos and other libraries might expect that .init is always working, so this could potentially be a problem. > > That's likely to break a _lot_ of generic code, because init is used heavily in template constraints and static if conditions as the way to get a value of that type to test. I think the new(er?) idiom is to use a lambda parameter to get the type instead. I.e., instead of: auto foo(T, U)(T t, U u) if (is(typeof(someOperation(T.init))) && is(typeof(otherOperation(U.init)))) { ... } you'd write: auto foo(T, U)(T t, U u) if (is(typeof((T t, U u) { someOperation(t); otherOperation(u); }))) { ... } This will work for types T, U that do not have default initialization (e.g., @disabled this(), like in the OP), types that for whatever reason have overridden .init, or types that are non-copyable, etc.. Nonetheless, .init is one of those things that are just assumed to always exist, so overriding it is not advisable. > It's also actually been argued before that it should be illegal to declare a symbol called init on any type, which is one reason why the init member was removed from TypeInfo. I'd strongly advise against anyone declaring a struct or class member named init whether it's @disabled or not. Indeed: https://issues.dlang.org/show_bug.cgi?id=7066 T -- Today's society is one of specialization: as you grow, you learn more and more about less and less. Eventually, you know everything about nothing. |
January 08, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Wednesday, 8 January 2020 at 07:03:26 UTC, Jonathan M Davis wrote:
> On Tuesday, January 7, 2020 5:23:48 PM MST Marcel via Digitalmars-d-learn wrote:
>> [...]
>
> In terms of an error message? Not really. You can put a pragma(msg, ""); in there, but that would always print, not just when someone tried to use it. I'd suggest that you just put the information in the documentation. If they write code that doesn't work because of you using
>
> [...]
Oops, it appears I didn't actually understand this part of the language. I'm coming from C++ and I assumed both languages did this the same way, but thankfully I found a workaround that doesn't require doing what I wrote in the initial post and is better in general.
I also agree with Adam, it can make for a nice little feature in D.
|
January 09, 2020 Re: @disable("reason") | ||||
---|---|---|---|---|
| ||||
Posted in reply to Marcel | On Wednesday, January 8, 2020 2:58:59 PM MST Marcel via Digitalmars-d-learn wrote: > On Wednesday, 8 January 2020 at 07:03:26 UTC, Jonathan M Davis > > wrote: > > On Tuesday, January 7, 2020 5:23:48 PM MST Marcel via > > > > Digitalmars-d-learn wrote: > >> [...] > > > > In terms of an error message? Not really. You can put a pragma(msg, ""); in there, but that would always print, not just when someone tried to use it. I'd suggest that you just put the information in the documentation. If they write code that doesn't work because of you using > > > > [...] > > Oops, it appears I didn't actually understand this part of the language. I'm coming from C++ and I assumed both languages did this the same way, but thankfully I found a workaround that doesn't require doing what I wrote in the initial post and is better in general. D took the approach of having all variables being default initalized with a value that's known at compile-time so that you don't risk dealing with garbage values as can happen in C/C++. For structs, the result is that default constructors aren't a thing, because the default vaule has to be a fixed value known at compile time, and that doesn't work with default constructors, which can run arbitrary code at runtime. And with non-default constructors, a struct is actually initialized with its default value before its constructor is even run. So, you don't risk reading garbage values in the object's member variables before properly initializing them. There are times when the lack of default constructors can be annoying, but it avoids a class of problems that C++ has, and overall, it works quite well. The ability to @disable this(); was added later in order to facilitate rarer cases where using the default value for a type would be problematic (e.g. when a type absolutely needed to have some code run at runtime when constructing it for it to work properly). However, because the language was designed around the idea that all objects would be default-initialized, disabling default initialization tends to make some typical stuff not work with that type (like out parameters or arrays, since they both require the init value). Ultimately, D's approach is a tradeoff. Some of the problems that you can have in C++ are eliminated, but it introduces some complications that don't exist in C++. > I also agree with Adam, it can make for a nice little feature in D. I doubt that there would be much resistance to it, but since you almost certainly need to read the documentation to understand how to use the type properly if it can't be default-initialized, I don't know that it would actually add much benefit in practice. Either way, it's not something that's come up often enough for anyone to push for such a language change. - Jonathan M Davis |
Copyright © 1999-2021 by the D Language Foundation