-
Notifications
You must be signed in to change notification settings - Fork 784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Module do
is often not evaluated, static initializers
#14362
Comments
do
is often not evaluateddo
is often not evaluated, static initializers
A recent conversation here #13905 |
@kerams, thanks. There's also this 100-message long discussion with @kspeakman with the conclusion here (remains available for a month or so), related to this code that should be possible with Then there's this SO question by @natalie-o-perret and this old issue: #890. I know it is not the first time this is raised, but I feel like we should find some low-hanging fruity way of making this less hurtful. Maybe including updating the docs and perhaps adding a "don't use |
@abelbraaksma Thanks so much for tracking this down. My use case is integrating with a .NET library (Dapper) that requires a static, one-time configuration. The initialization adds support for F# Option types. Because my library's module It seems at first that user-facing initialization is an alternative solution. That is, requiring the user to call Note: User-facing initialization is preferable when startup has a significant cost. So that the user doesn't pay it unexpectedly, such as in a performance-critical section. Here, that is not the case. My use case exactly fits a static constructor. But I want to use idiomatic F# with modules (which compile to static classes) rather than classes. I thought module |
Woule a solution be to treat it on par with static constructors of regular types? What I am afraid is that we would need to guard around this in optimizations, preventing inlining in particular. |
Right now (AFAICS) the generation is driven by "doesSomething" at https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/IlxGen.fs#L10241 The obviously bad thing is that a refactoring property of "moving modules around" is not stable.
|
Classes have A module |
I don't think this should become a language suggestion (@0101 marking this An alternative solution would be to allow I'd rather change this one into a warning, as clearly this surprises even seasoned programmers and we won't be able to fix the existing behavior: code in the wild may rely on it. |
I like the ModuleIntializerAttribute proposal for library code, leaning on the runtime to ensure an exactly-once execution. |
@T-Gro, yes. Keep in mind that I'll turn it into a language suggestion... Hmm, there was a language suggestion in the past, but it was turned down. Perhaps we can see if there's room for now? Original proposal: fsharp/fslang-suggestions#1024 |
The original suggestion did not have motivating examples where the existing F# features do not suffice and lead to unexpected (by the programmer) missed calls. This issue has it. |
This is a known issue, or feature so to say. The language spec (screenshot below) has an extensive list of reasons when static initialisers aren't evaluated. The spec doesn't specifically mention
module do
there, but it applies.The docs say about this just the following:
This isn't very clear and arguably not true. The common idea (as often stated in online posts) is that the
do
binding is executed "on first access", basically similarly to howstatic do
works in classes. This is incorrect.If you manage to find the section in the F# spec and you disentangle it, it turns out that the
do
is only executed if it's either in the same file asmain
(or a script file), or if it has a referential dependency on mutable state (i.e., alet mutable
), or a normallet
that happens to be bound to something not "constant-like".Repro steps
There are many subtleties to this, but as a "simplest example":
In another file, or reference the above through a project reference, call
Test.f()
. You will notice thatHello world
is never printed.Expected behavior
Module
do
gets executed on first access to the module.Actual behavior
Module
do
does not get executed ever. However, as soon as you try to reproduce it by dumping it in a script file or copying it to FSI, you will see that it does get executed.Known workarounds
Force a referential dependency on a mutual. This is far from trivial and has led to many discussions and hard-to-diagnose bugs.
Now the
do
will get executed:Related information
Perhaps we can improve by issuing a warning when initializing code is not being executed? Or, conversely, add an opt-in attribute that would add the
module do
to the static constructor of the module, as opposed to theStartupCode$File
cctor (which contains the static fields, forcing the initialization only when there's some mutable state or non-trivial let binding).From the spec:
The text was updated successfully, but these errors were encountered: