This is a very simple test case. The thread runs to completion end then throws an exception:
Error: Win32 Exception
import std.thread;
void main()
{
int g;
int run()
{
for(int i = 0; i < 1000; ++i)
++g;
return 0;
}
auto t = new Thread(run);
t.start;
t.wait;
}
Comment #1 by torhu — 2008-06-22T14:37:24Z
I hate to be the one to say this, but there's an important typo in your test case.
try this diff:
-auto t = new Thread(run);
+auto t = new Thread(&run);
You ended up calling the ctor that takes a size_t instead of the function pointer one.
Comment #2 by bartosz — 2008-06-22T16:20:06Z
Then this is an even more serious language-design problem. Run was called without me using the call syntax. This might be okay for no-argument methods, but it's a bad idea for non-member functions, as my example illustrates.
Comment #3 by bugzilla — 2008-06-27T17:12:17Z
This is not a language design problem, it is intended to work this way. The fact that run() worked as an argument is because run() returns an int and one of the constructors for Thread takes a size_t.
Comment #4 by bartosz — 2008-06-28T13:49:46Z
For me there's no doubt that it's a design flaw in the language and I'll try to explain my reasoning.
For people coming from C/C++: A function name in C is treated as a function pointer. It's okay for D to make this syntax illegal. It's not okay to give this syntax a completely different meaning (function call), expecially if there are situations where the error is undetectable. I was lucky the the result of run() was too small for the stack size, otherwise my unit test would run without a problem.
Program understanding: How does the compiler know that "run" is a call and not a variable? Because it has the symbol table. The symbol might be defined arbitrarily far from the point of use. That's fine for the compiler but not for the programmer who's reading somebody else's code. Now the reviewer must chase multiple files in order to figure out if something is a function call or a variable. You can't really say that it shouldn't matter for the reviewer whether a given statement is a function call.
Notice that the analogous method-call syntax is not bad, because it has a different context. There is always an object involved. Without any knowledge of the symbol table, the programmer assumes that it's an access to a public data member. The fact that it expands to a method call is the matter of the object, whose designer decided to hide this implementation detail. This is very much in the spirit of object-oriented programming--implementation hiding. But there is a well-defined owner/encapsulator of implementation details--the object.
From the abstract point of view, when you allow a symbol to transparently expand into a function call, you are making a unification. The new abstraction is a "variable or a function call". What is this abstraction? Do you have a name for it? If you don't, it's just a hack that saves two keystrokes and makes the code more obfuscated. Keystroke-saving improvements should not be a design goal in itself.
Note that there is an analogous abstration for method calls. It's called a "property".
When we discuss this topic in writing, you use the syntax "run()" rather than "run". That's because you want to be clear that you mean a function call. When you write a program you also communicate with other programmers. You should be as clear about the meaning as you are in our discussion. If I were reviewing somebody else's code I would demand they use the function-call syntax.
Comment #5 by davidl — 2008-06-29T22:27:10Z
yes, this is really nasty.
One solution is change the interface of Thread class. But it's not a good solution.
Because that relies on the higher interface design quality of lib developers(and it's not necessary in other langs).
So perhaps a qualifier "property" like C# to explicit declare if this should/could be used as a property by property syntax is better.
For this case, the real problem is that "run" in normal sense is not a "property", but it can be misused in property syntax.
Comment #6 by bartosz — 2008-06-30T12:08:49Z
In the meanwhile I found an example of ambiguity. Here’s some sample code from the documentation of std.algorithm:
inPlace!(writeln)(arr1); // print the array
Why isn't writeln executed on the spot? Are we passing a function of no arguments as an alias parameter to a template, or are we evaluating the function and passing its result to the template? Note that here additional parentheses would change the meaning of code.
inPlace!(writeln())(arr1);
Comment #7 by shro8822 — 2008-06-30T12:16:57Z
switch writeln to a CTFE able function and that would be a problem. In that exact cases the alias must be used because it's the only compile-time option. And then I think it's only 2.0 that allows values as aliases.
Comment #8 by dmitry.olsh — 2018-05-15T14:32:43Z
import core.thread;
void main()
{
int g;
int run()
{
for(int i = 0; i < 1000; ++i)
++g;
return 0;
}
auto t = new Thread(run);
t.start;
t.wait;
}
Now it doesn't fail and I fail to see how we fix the general since optional parens became a thing after UFCS was finally fully implemented.
Comment #9 by dfj1esp02 — 2018-05-16T12:12:16Z
> Now it doesn't fail
Then it's a subtle bug that can ship to the user.
> and I fail to see how we fix the general since optional
> parens became a thing after UFCS was finally fully implemented.
The compiler can detect overload ambiguity between constructors taking int and function pointer.
> This might be okay for no-argument methods
FWIW, C# takes method delegates without ampersand syntax much like C does for functions.
Comment #10 by razvan.nitu1305 — 2019-10-10T14:26:37Z
Compiling:
import core.thread;
void main()
{
int g;
int run()
{
for(int i = 0; i < 1000; ++i)
++g;
return 0;
}
auto t = new Thread(run);
t.start;
t.wait;
}
Yields:
test.d(11): Error: class core.thread.Thread member this is not accessible
test.d(13): Error: no property wait for type core.thread.Thread
Is this satisfactory?
Comment #11 by dfj1esp02 — 2019-10-25T15:15:36Z
Self-contained test case:
---
void f(int);
void f(int function());
int g();
void h()
{
f(g);
f(&g);
}
---
Comment #12 by dfj1esp02 — 2019-10-25T15:18:14Z
If the ambiguity happened spontaneously in phobos, it's plausible it can reemerge or happen elsewhere. Though it's a language change, so it can be required to go through DIP?
Comment #13 by razvan.nitu1305 — 2019-10-25T16:19:10Z
(In reply to anonymous4 from comment #11)
> Self-contained test case:
> ---
> void f(int);
> void f(int function());
> int g();
> void h()
> {
> f(g);
> f(&g);
> }
> ---
I don't get it. This code compiles just fine, as it should. f(g) call s f(int) and f(&g) calls f(int function()). This seems reasonable to me.
Comment #14 by dfj1esp02 — 2019-10-28T15:16:18Z
Well, it's an enhancement request. Per the title this code is error prone and ambiguous for people with C background, although it requires specific design to trigger.
Comment #15 by josipp — 2022-04-03T19:33:36Z
(In reply to Bartosz Milewski from comment #4)
> For me there's no doubt that it's a design flaw in the language and I'll try
> to explain my reasoning.
>
> For people coming from C/C++: A function name in C is treated as a function
> pointer. It's okay for D to make this syntax illegal. It's not okay to give
> this syntax a completely different meaning (function call), expecially if
> there are situations where the error is undetectable. I was lucky the the
> result of run() was too small for the stack size, otherwise my unit test
> would run without a problem.
>
> Program understanding: How does the compiler know that "run" is a call and
> not a variable? Because it has the symbol table. The symbol might be defined
> arbitrarily far from the point of use. That's fine for the compiler but not
> for the programmer who's reading somebody else's code. Now the reviewer must
> chase multiple files in order to figure out if something is a function call
> or a variable. You can't really say that it shouldn't matter for the
> reviewer whether a given statement is a function call.
>
> Notice that the analogous method-call syntax is not bad, because it has a
> different context. There is always an object involved. Without any knowledge
> of the symbol table, the programmer assumes that it's an access to a public
> data member. The fact that it expands to a method call is the matter of the
> object, whose designer decided to hide this implementation detail. This is
> very much in the spirit of object-oriented programming--implementation
> hiding. But there is a well-defined owner/encapsulator of implementation
> details--the object.
>
> From the abstract point of view, when you allow a symbol to transparently
> expand into a function call, you are making a unification. The new
> abstraction is a "variable or a function call". What is this abstraction? Do
> you have a name for it? If you don't, it's just a hack that saves two
> keystrokes and makes the code more obfuscated. Keystroke-saving improvements
> should not be a design goal in itself.
>
> Note that there is an analogous abstration for method calls. It's called a
> "property".
>
> When we discuss this topic in writing, you use the syntax "run()" rather
> than "run". That's because you want to be clear that you mean a function
> call. When you write a program you also communicate with other programmers.
> You should be as clear about the meaning as you are in our discussion. If I
> were reviewing somebody else's code I would demand they use the
> function-call syntax.
I second this wholeheartedly; taking a page from Scala's book, they have
a very similar problem on their hands, and decided to disallow no-parens invocations
for anything that is not a property at some point: their reasoning was
that the possibility of some call producing side effects must be clearly stated
by treating the invocation as a real function call; after all, that is what
the `pure` attribute is kind of trying to emulate in D, but is completely undercut
by this language quirk!
Comment #16 by robert.schadek — 2024-12-13T17:48:31Z