F# Module 1 – Start here: F# for .NET developers
F# projects, file order and compiler messages
Create F# console, library and test projects with dotnet new, fix errors that file order causes, read FS diagnostic codes and control warnings in F# 10.
What you will learn
- Create console, library and test projects with dotnet new and explain the Compile order of an .fsproj
- Fix a 'not defined' error caused by file order instead of reaching for a recursive module
- Read F# diagnostics by their codes, such as FS0001, FS0039, FS0025 and FS0020
- Configure warnings for a few lines with
- Choose between top-level code in the last file and an explicit [<EntryPoint>] function
Before you start
What you’ll learn · Before you start
What you will learn
- Create console, library and test projects with dotnet new and explain the Compile order of an .fsproj
- Fix a 'not defined' error caused by file order instead of reaching for a recursive module
- Read F# diagnostics by their codes, such as FS0001, FS0039, FS0025 and FS0020
- Configure warnings for a few lines with
- Choose between top-level code in the last file and an explicit [<EntryPoint>] function
Before you start
On this page
A new F# console project has one source file and builds in seconds. Add two more files and list them in the wrong order, and the build stops with error FS0039 although every name is spelled right, because F# compiles files in the order the project lists them and a file can only use what came before it.
This lesson creates the three kinds of project you will use most, reads a project file, breaks and fixes its file order, reads the compiler’s messages by their codes, controls warnings with the directives F# 10 introduced, and gives a program an entry point with an exit status.
dotnet new writes a project file that lists every source file
dotnet new creates a project from a template, and -lang "F#" picks the F# version of it. This session makes a
console app, a class library and an xUnit test project, then runs the app:
from session import run
# Three F# projects from the SDK's templates: a console app, a class library and a test project (made without
# restoring its packages, which would need the network); the line of the SDK's F# settings that stops F# projects
# from compiling every source file in their folder; then the console app's build output, with and without
# FSharp.Core.dll.
run("""
dotnet new console -lang "F#" -o Shop
cat Shop/Shop.fsproj
cat Shop/Program.fs
grep "Do not glob" "$DOTNET_ROOT/sdk/10.0.401/FSharp/Microsoft.FSharp.NetSdk.props" | tr -s ' '
dotnet new classlib -lang "F#" -o Pricing
cat Pricing/Pricing.fsproj
dotnet new xunit -lang "F#" -o Pricing.Tests --no-restore
cat Pricing.Tests/Pricing.Tests.fsproj
dotnet run --project Shop
ls Shop/bin/Debug/net10.0
rm Shop/bin/Debug/net10.0/FSharp.Core.dll
!dotnet Shop/bin/Debug/net10.0/Shop.dll
""") Output
$ dotnet new console -lang "F#" -o Shop
The template "Console App" was created successfully.
Processing post-creation actions...
Restoring Shop/Shop.fsproj:
Determining projects to restore...
Restored Shop/Shop.fsproj (in …).
Restore succeeded.
$ cat Shop/Shop.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
$ cat Shop/Program.fs
// For more information see https://aka.ms/fsharp-console-apps
printfn "Hello from F#"
$ grep "Do not glob" "$DOTNET_ROOT/sdk/10.0.401/FSharp/Microsoft.FSharp.NetSdk.props" | tr -s ' '
<EnableDefaultCompileItems Condition=" '$(EnableDefaultCompileItems)' == '' ">false</EnableDefaultCompileItems> <!-- Do not glob F# source files -->
<EnableDefaultNoneItems Condition=" '$(EnableDefaultNoneItems)' == '' ">false</EnableDefaultNoneItems> <!-- Do not glob F# none items-->
$ dotnet new classlib -lang "F#" -o Pricing
The template "Class Library" was created successfully.
Processing post-creation actions...
Restoring Pricing/Pricing.fsproj:
Determining projects to restore...
Restored Pricing/Pricing.fsproj (in …).
Restore succeeded.
$ cat Pricing/Pricing.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
</Project>
$ dotnet new xunit -lang "F#" -o Pricing.Tests --no-restore
The template "xUnit Test Project" was created successfully.
$ cat Pricing.Tests/Pricing.Tests.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Compile Include="Tests.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
</Project>
$ dotnet run --project Shop
Hello from F#
$ ls Shop/bin/Debug/net10.0
cs
de
es
fr
FSharp.Core.dll
it
ja
ko
pl
pt-BR
ru
Shop
Shop.deps.json
Shop.dll
Shop.pdb
Shop.runtimeconfig.json
tr
zh-Hans
zh-Hant
$ rm Shop/bin/Debug/net10.0/FSharp.Core.dll
$ dotnet Shop/bin/Debug/net10.0/Shop.dll
Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Core, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
File name: 'FSharp.Core, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
at <StartupCode$Shop>.$Program.main@()
[exit status 134]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 new_session.py
Each project file: An F# project's .fsproj file: an MSBuild file that names the target framework, the package references and, as Compile items, every source file in the order the compiler reads them. is short. The console app’s says it is an executable for net10.0 and lists
one Compile item, Program.fs; the library has Library.fs and asks for an XML documentation file; the test project
lists Tests.fs and four package references, among them xunit 2.9.3. A C# project compiles every .cs file in its
folder by default; the first line of the SDK’s F# settings that the session printed switches that off for F#, with the
comment “Do not glob F# source files”. An F# project compiles exactly the files its Compile items list, in that order, so a
new file in the folder is ignored until you add it to the list.
The console app’s Program.fs is two lines with no main: its top-level code: Statements written directly in the last file of an F# application, outside any function. They run from top to bottom when the program starts: the implicit entry point. runs from
top to bottom when the program starts. Its build output holds the program, Shop.dll, and beside it FSharp.Core.dll
and thirteen folders of FSharp.Core’s localized resources, one for each language. Delete FSharp.Core.dll and the program cannot start: the
runtime stops with FileNotFoundException before the first line runs, which is why the FSharp.Core notes say to always
deploy it with a compiled application.
| Command | Source file it makes | What it builds |
|---|---|---|
dotnet new console -lang "F#" |
Program.fs, top-level code |
a console app |
dotnet new classlib -lang "F#" |
Library.fs, a module |
a class library, .dll |
dotnet new xunit -lang "F#" |
Tests.fs |
a test project |
The test template's xUnit version
The F# xUnit template of SDK 10.0.401 still references xunit 2.9.3, a 2.x version, as the recorded project file shows. This track’s testing module uses xUnit v3; check the package versions of any template before you build on it.
A file can use only what the files above it define
Here is a small shop program in three files. Domain.fs defines the record type Item, Pricing.fs totals an order of
items, and Program.fs prints a total:
order-right/Domain.fs
module Shop.Domain
type Item = { Name: string; Price: decimal; Qty: int } order-right/Pricing.fs
module Shop.Pricing
open Shop.Domain
let lineTotal item = item.Price * decimal item.Qty
let orderTotal items = items |> List.sumBy lineTotal order-right/Program.fs
open Shop.Domain
open Shop.Pricing
let order =
[ { Name = "Pen"; Price = 25m; Qty = 4 }
{ Name = "Pad"; Price = 60m; Qty = 2 } ]
printfn "Total: %M" (orderTotal order) order-right/Orders.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Domain.fs" />
<Compile Include="Pricing.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
</Project> The same three files went into four projects that differ only in their Compile order and, once, a rec keyword:
from session import run
# The same three files in four projects: Pricing.fs before Domain.fs, the same with Pricing a rec module, the order
# that works, and Program.fs first. -v q (quiet) leaves only the messages, each printed when it happens and again in
# MSBuild's summary.
run("""
!dotnet build order-wrong -v q
!dotnet build order-rec -v q
dotnet build order-right -v q
dotnet run --project order-right
!dotnet build program-first -v q
""") Output
$ dotnet build order-wrong -v q
order-wrong/Pricing.fs(3,6): error FS0039: The namespace or module 'Shop' is not defined. [order-wrong/Orders.fsproj]
order-wrong/Pricing.fs(5,22): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-wrong/Orders.fsproj]
order-wrong/Pricing.fs(5,43): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-wrong/Orders.fsproj]
Build FAILED.
order-wrong/Pricing.fs(3,6): error FS0039: The namespace or module 'Shop' is not defined. [order-wrong/Orders.fsproj]
order-wrong/Pricing.fs(5,22): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-wrong/Orders.fsproj]
order-wrong/Pricing.fs(5,43): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-wrong/Orders.fsproj]
0 Warning(s)
3 Error(s)
Time Elapsed …
[exit status 1]
$ dotnet build order-rec -v q
order-rec/Pricing.fs(3,11): error FS0039: The namespace 'Domain' is not defined. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(3,11): error FS0039: The namespace 'Domain' is not defined. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(3,11): error FS0039: The namespace 'Domain' is not defined. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(5,22): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(5,43): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-rec/Orders.fsproj]
Build FAILED.
order-rec/Pricing.fs(3,11): error FS0039: The namespace 'Domain' is not defined. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(3,11): error FS0039: The namespace 'Domain' is not defined. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(3,11): error FS0039: The namespace 'Domain' is not defined. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(5,22): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-rec/Orders.fsproj]
order-rec/Pricing.fs(5,43): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [order-rec/Orders.fsproj]
0 Warning(s)
5 Error(s)
Time Elapsed …
[exit status 1]
$ dotnet build order-right -v q
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet run --project order-right
Total: 220
$ dotnet build program-first -v q
program-first/Program.fs(1,1): error FS0222: Files in libraries or multiple-file applications must begin with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace' or 'module SomeNamespace.SomeModule'. Only the last source file of an application may omit such a declaration. [program-first/Orders.fsproj]
Build FAILED.
program-first/Program.fs(1,1): error FS0222: Files in libraries or multiple-file applications must begin with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace' or 'module SomeNamespace.SomeModule'. Only the last source file of an application may omit such a declaration. [program-first/Orders.fsproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 order_session.py
In order-wrong, Pricing.fs comes before Domain.fs, and the build fails with FS0039, “The namespace or module ‘Shop’
is not defined”, followed by two FS0072 errors that are only consequences: without Item, the compiler cannot tell what
item.Price means. Making Pricing a rec module in order-rec does not help: the build still fails with FS0039,
because rec only lets the declarations inside one module refer to each other. In the right order the program builds and prints Total: 220. And
with Program.fs first, the build stops with FS0222: only the last file of an application may leave out its module
declaration. Step through what the compiler sees in the right order and in the swapped one:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The order is in the project. The project file lists the source files as Compile items, and the compiler reads them in exactly that order, from the first item to the last.
- Domain.fs first. Domain.fs defines the module Shop.Domain and its record type Item. Nothing comes before it, so it can use only FSharp.Core and .NET.
- Pricing.fs sees Domain. Pricing.fs opens Shop.Domain, which the compiler has already read, so item.Price and item.Qty have a known type.
- Program.fs comes last. Program.fs opens both modules and calls orderTotal from Pricing.fs. As the last file of an application it needs no module declaration, and its top-level code is what the program runs.
- Swap the first two. In order-wrong the project lists Pricing.fs before Domain.fs. Now Pricing.fs is read first, and Shop.Domain does not exist yet.
- Fix the order, not the code. The cure is the order. List each file after the files it uses, so every arrow points upwards; making Pricing a rec module does not help, because rec never lets a file see a later one.
Fixing the order with rec
Wrong: making a module or namespace rec until the error goes away. Right: move the file in the project’s Compile
list so that it comes after every file it uses. rec is for types and functions in one file that refer to each
other, and it cannot make a file see a later one.
The rule is deliberate. Because a file sees only the files above it, dependencies between files can point one way only, and the list in the project file reads as the program’s layers, from the domain types at the top to the program at the bottom. No file can depend on a later one, so files can never form a cycle.
Key idea
F# compiles the files in the order of the project’s Compile items, and each file sees only the files above it: fix a “not defined” error between files by moving the file, never by adding rec.
Checkpoint
Check: What dotnet new writes · File order in a project
The FS code says what kind of problem the compiler found
Every F# compiler message has a code, FS and four digits, and the code tells you more than the text: the same code
always means the same kind of problem. Two projects show the ones you will meet first. warnings builds, with three
warnings, and errors does not build at all:
warnings/Rules.fs · warnings/Rules.fs
module Rules
type Tier =
| Standard
| Member
| Partner
let rate tier =
match tier with
| Standard -> 0m
| Member -> 0.05m
let label tier =
match tier with
| Standard -> "standard"
| _ -> "other"
| Member -> "member"
let report (prices: decimal list) =
List.map (fun p -> p * 2m) prices
printfn "checked %d prices" prices.Length errors/Broken.fs · errors/Broken.fs
module Broken
let total = 2 + 1.5
let describe n =
if n > 0 then "positive"
let shout name = name.ToUpper()
let missing = totl from session import run
# One project that builds with three warnings, and one that fails with five errors.
run("""
dotnet build warnings -v q
!dotnet build errors -v q
""") Output
$ dotnet build warnings -v q
warnings/Rules.fs(9,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [warnings/Warnings.fsproj]
warnings/Rules.fs(17,7): warning FS0026: This rule will never be matched [warnings/Warnings.fsproj]
warnings/Rules.fs(20,5): warning FS0020: The result of this expression has type 'decimal list' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'. [warnings/Warnings.fsproj]
Build succeeded.
warnings/Rules.fs(9,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [warnings/Warnings.fsproj]
warnings/Rules.fs(17,7): warning FS0026: This rule will never be matched [warnings/Warnings.fsproj]
warnings/Rules.fs(20,5): warning FS0020: The result of this expression has type 'decimal list' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'. [warnings/Warnings.fsproj]
3 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet build errors -v q
errors/Broken.fs(3,17): error FS0001: The type 'float' does not match the type 'int' [errors/Errors.fsproj]
errors/Broken.fs(3,15): error FS0043: The type 'float' does not match the type 'int' [errors/Errors.fsproj]
errors/Broken.fs(6,19): error FS0001: This 'if' expression is missing an 'else' branch. Because 'if' is an expression, and not a statement, add an 'else' branch which also returns a value of type 'string'. [errors/Errors.fsproj]
errors/Broken.fs(8,18): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [errors/Errors.fsproj]
errors/Broken.fs(10,15): error FS0039: The value or constructor 'totl' is not defined. Maybe you want one of the following: total [errors/Errors.fsproj]
Build FAILED.
errors/Broken.fs(3,17): error FS0001: The type 'float' does not match the type 'int' [errors/Errors.fsproj]
errors/Broken.fs(3,15): error FS0043: The type 'float' does not match the type 'int' [errors/Errors.fsproj]
errors/Broken.fs(6,19): error FS0001: This 'if' expression is missing an 'else' branch. Because 'if' is an expression, and not a statement, add an 'else' branch which also returns a value of type 'string'. [errors/Errors.fsproj]
errors/Broken.fs(8,18): error FS0072: The type of this expression could not be inferred before accessing its members. Add a type annotation, e.g. (expr: SomeType), to constrain the type before this point. [errors/Errors.fsproj]
errors/Broken.fs(10,15): error FS0039: The value or constructor 'totl' is not defined. Maybe you want one of the following: total [errors/Errors.fsproj]
0 Warning(s)
5 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 diagnostics_session.py
Each message starts with the file and the line and column, Rules.fs(9,11), then warning or error and the code.
MSBuild prints each message twice: once as the compiler reports it and again in the summary at the end.
| Code | What it means, and the case above | The usual fix |
|---|---|---|
| FS0001 | two types that must agree do not: 2 + 1.5, an if without else |
convert explicitly, or add the branch |
| FS0039 | a name is not defined at this point: totl, or a file listed too late |
fix the spelling, the open or the file order |
| FS0072 | a member is used before the type is known: name.ToUpper() |
annotate the parameter, (name: string) |
| FS0025 | a match misses a case: rate has no rule for Partner |
add the case, or _ when that is truly right |
| FS0026 | a rule can never match: Member after _ |
move it above the catch-all, or delete it |
| FS0020 | a result is silently thrown away: the unused List.map |
use the result, or pipe it to ignore |
The compiler’s own text often contains the fix. The FS0001 for the if explains that if is an expression, not a
statement, so without an else it cannot produce a string, and the FS0039 for totl suggests total. Read the
first error first: the second error of 2 + 1.5, FS0043, repeats the first, as the FS0072 errors after a missing
module did above.
Checkpoint
Check: Reading FS codes
Warnings can be silenced for a few lines, or made errors for a project
A warning that you have decided is acceptable should be silenced exactly where it applies, and no further. In F# 10,
#nowarn turns a warning off until a matching #warnon or the end of the file; before F# 10 a #nowarn lasted to the
end of the file, and in scripts it applied to the whole compilation. Here the incomplete match in rate is deliberate
and the one in label is not:
module Tiers
type Tier =
| Standard
| Member
| Partner
// Partner is priced elsewhere, so this incomplete match is deliberate.
#nowarn 25
let rate tier =
match tier with
| Standard -> 0m
| Member -> 0.05m
#warnon 25
let label tier =
match tier with
| Standard -> "standard"
| Member -> "member" from session import run
# Warning controls: a #nowarn/#warnon pair (F# 10, then the same file compiled as F# 9), an opt-in warning for
# unused values, and every warning treated as an error. --no-incremental compiles again although no file changed.
run("""
dotnet build scoped -v q
!dotnet build scoped -v q --no-incremental -p:LangVersion=9.0
dotnet build warnings -v q --no-incremental -p:WarnOn=1182
!dotnet build warnings -v q --no-incremental -p:TreatWarningsAsErrors=true
""") Output
$ dotnet build scoped -v q
scoped/Tiers.fs(17,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [scoped/Tiers.fsproj]
Build succeeded.
scoped/Tiers.fs(17,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [scoped/Tiers.fsproj]
1 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet build scoped -v q --no-incremental -p:LangVersion=9.0
scoped/Tiers.fs(14,1): error FS3350: Feature 'Support for scoped enabling / disabling of warnings by #warn and #nowarn directives, also inside modules' is not available in F# 9.0. Please use language version 10.0 or greater. [scoped/Tiers.fsproj]
Build FAILED.
scoped/Tiers.fs(14,1): error FS3350: Feature 'Support for scoped enabling / disabling of warnings by #warn and #nowarn directives, also inside modules' is not available in F# 9.0. Please use language version 10.0 or greater. [scoped/Tiers.fsproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
$ dotnet build warnings -v q --no-incremental -p:WarnOn=1182
warnings/Rules.fs(9,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [warnings/Warnings.fsproj]
warnings/Rules.fs(17,7): warning FS0026: This rule will never be matched [warnings/Warnings.fsproj]
warnings/Rules.fs(20,5): warning FS0020: The result of this expression has type 'decimal list' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'. [warnings/Warnings.fsproj]
warnings/Program.fs(3,9): warning FS1182: The value 'unused' is unused [warnings/Warnings.fsproj]
Build succeeded.
warnings/Rules.fs(9,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [warnings/Warnings.fsproj]
warnings/Rules.fs(17,7): warning FS0026: This rule will never be matched [warnings/Warnings.fsproj]
warnings/Rules.fs(20,5): warning FS0020: The result of this expression has type 'decimal list' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'. [warnings/Warnings.fsproj]
warnings/Program.fs(3,9): warning FS1182: The value 'unused' is unused [warnings/Warnings.fsproj]
4 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet build warnings -v q --no-incremental -p:TreatWarningsAsErrors=true
warnings/Rules.fs(9,11): error FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [warnings/Warnings.fsproj]
warnings/Rules.fs(17,7): error FS0026: This rule will never be matched [warnings/Warnings.fsproj]
warnings/Rules.fs(20,5): error FS0020: The result of this expression has type 'decimal list' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'. [warnings/Warnings.fsproj]
Build FAILED.
warnings/Rules.fs(9,11): error FS0025: Incomplete pattern matches on this expression. For example, the value 'Partner' may indicate a case not covered by the pattern(s). [warnings/Warnings.fsproj]
warnings/Rules.fs(17,7): error FS0026: This rule will never be matched [warnings/Warnings.fsproj]
warnings/Rules.fs(20,5): error FS0020: The result of this expression has type 'decimal list' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'. [warnings/Warnings.fsproj]
0 Warning(s)
3 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 warnings_session.py
Only label’s FS0025 is reported: the pair of directives silenced the warning for rate alone. Compiled as F# 9, the
same file fails with FS3350, because the feature needs language version 10.0, a reminder that LangVersion changes
what a file may contain. The F# 10 notes list the stricter rules that came with it: no space between # and nowarn,
and no multi-line or empty warn directives.
Two project settings act on every file. <WarnOn>1182</WarnOn> switches on an opt-in warning, here FS1182 for the unused
value in main; the compiler options page lists the other opt-in warnings. <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
turns every warning into an error, so the build that succeeded with three warnings fails with three errors. Set it in
the build that checks every change, and warnings cannot pile up unnoticed.
One more difference matters when you try diagnostics in a script. [<TailCall>] asks the compiler to check that a
function’s recursive calls are tail calls, and sumTo adds after its call returns:
module Sum
// [<TailCall>] asks the compiler to check that every recursive call is a tail call. This one is not:
// the addition happens after the call returns.
[<TailCall>]
let rec sumTo n =
if n = 0 then 0 else n + sumTo (n - 1) from session import run
# The same non-tail-recursive function with [<TailCall>], compiled by dotnet build and run as a script.
run("""
dotnet build tailcall -v q
dotnet fsi --nologo tailcall/sum.fsx
""") Output
$ dotnet build tailcall -v q
tailcall/Sum.fs(7,30): warning FS3569: The member or function 'sumTo' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. [tailcall/Sum.fsproj]
Build succeeded.
tailcall/Sum.fs(7,30): warning FS3569: The member or function 'sumTo' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. [tailcall/Sum.fsproj]
1 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet fsi --nologo tailcall/sum.fsx
5050
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 tailcall_session.py
dotnet build reports FS3569, and dotnet fsi runs the same code as a script and prints 5050 without a word: F#
Interactive does not run every check that a build runs. Check warnings with dotnet build before you trust them to be
absent.
Checkpoint
Check: Controlling warnings
The program starts in the last file, at the top or at main
An F# application starts in its last file in one of two ways. With top-level code, as in the console template, the
statements of the last file run from top to bottom. With an explicit entry point: A function marked [<EntryPoint>] of type string array -> int, the last declaration of the last file. It receives the command-line arguments, and its result is the exit status., a
function marked [<EntryPoint>] of type string array -> int receives the command-line arguments, and the number it
returns is the program’s exit status:
// The entry point: the last declaration of the last file. Its int result is the exit status of the process.
[<EntryPoint>]
let main args =
match args with
| [| name |] ->
printfn "Hello, %s" name
0
| _ ->
eprintfn "usage: greet <name>"
64 from session import run
# An explicit entry point whose result is the exit status, then an entry point that is not in the last file.
run("""
dotnet run --project greet -- Asha
!dotnet run --project greet
!dotnet build entry-early -v q
""") Output
$ dotnet run --project greet -- Asha
Hello, Asha
$ dotnet run --project greet
usage: greet <name>
[exit status 64]
$ dotnet build entry-early -v q
entry-early/Program.fs(4,5): error FS0433: A function labeled with the 'EntryPointAttribute' attribute must be the last declaration in the last file in the compilation sequence. [entry-early/Early.fsproj]
Build FAILED.
entry-early/Program.fs(4,5): error FS0433: A function labeled with the 'EntryPointAttribute' attribute must be the last declaration in the last file in the compilation sequence. [entry-early/Early.fsproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 entry_session.py
With one argument, greet printed its greeting and returned 0; with none it wrote the usage line to standard error and
returned 64, which the shell reported as the exit status. The args array holds only the arguments, without the
program’s name. The last build shows the rule’s other half: in entry-early, the function marked [<EntryPoint>] is in
the first of two files, and the build fails with FS0433, because the entry point must be the last declaration in the
last file.
| Top-level code | [<EntryPoint>] |
|
|---|---|---|
| Where | the statements of the last file | the last declaration of the last file |
| Arguments | System.Environment.GetCommandLineArgs() |
the args parameter |
| Exit status | exit n, or 0 at the end |
the function’s int result |
| Suits | small programs and the template | programs whose main you want to test |
Microsoft’s guide to explicit entry points gives the three reasons to choose one: arguments as a parameter, an exit code
as a return value, and a main that a test can call.
Checkpoint
Check: Where the program starts
Practise: projects, order and messages
These questions draw on the sessions above: file orders, codes, warning controls and entry points. The idea you have practised least is asked about first, and each answer explains itself as soon as you pick it:
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): why does the order of files in an F# project matter, and what is the rule?
F# compiles the files of a project in the order of the project file’s Compile items, and each file can use only what
the files before it define, so a file that uses a type from a later file fails with FS0039, “not defined”. The order is
part of the design: dependencies between files point one way, from later files to earlier ones, so a project cannot
contain a cycle between files, and the list reads as the program’s layers. The fix for an ordering error is to move the
file in the project file’s list, not to make modules recursive: rec lets the
declarations inside one module refer to each other, but never lets a file see a later one. In an application, the last
file is where the program starts.
Two harder questions follow, about a build that breaks when a file is added and about a team’s warnings:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A teammate adds Discounts.fs to a working F# project and moves some pricing code into it. The build now fails with FS0039 in two files and a dozen FS0072 errors. Walk through how you diagnose and fix it.
-
Mid to senior
Your team's F# solution builds with 140 warnings that everyone has learned to ignore. Propose a warnings policy, and explain how you would get from 140 warnings to it without stopping feature work.
Key takeaways
dotnet new console,classlibandxunitwith-lang "F#"make projects whose.fsprojlists every source file as aCompileitem; a built application shipsFSharp.Core.dllbeside it.- F# compiles files in the project’s order and a file sees only the files above it, so “not defined” errors between
files are fixed by moving the file, never with
rec. - An FS code names the kind of problem: FS0001 types that disagree, FS0039 a missing name, FS0025 an incomplete match, FS0020 a discarded result; read the first error first.
- In F# 10,
#nowarnlasts until a matching#warnon,WarnOnenables opt-in warnings,TreatWarningsAsErrorsstops the build on any warning, and only a build reports some warnings, such as FS3569. - An application starts in its last file, with top-level code or with an
[<EntryPoint>]function whoseintresult is the exit status.
Terms in this lesson
- entry point
- A function marked [<EntryPoint>] of type string array -> int, the last declaration of the last file. It receives the command-line arguments, and its result is the exit status.
- project file
- An F# project's .fsproj file: an MSBuild file that names the target framework, the package references and, as Compile items, every source file in the order the compiler reads them.
- top-level code
- Statements written directly in the last file of an F# application, outside any function. They run from top to bottom when the program starts: the implicit entry point.
References
- Get started with F# with the .NET CLI (Microsoft Learn)
- dotnet new (Microsoft Learn)
- Modules (Microsoft Learn)
- Compiler errors and warnings (Microsoft Learn)
- FS0001, error from adding a type equation (Microsoft Learn)
- FS0025, incomplete pattern match (Microsoft Learn)
- Compiler directives (Microsoft Learn)
- Compiler options (Microsoft Learn)
- What's new in F# 10: scoped warning suppression (Microsoft Learn)
- RFC FS-1146: scoped #nowarn (F# language design (fsharp/fslang-design))
- Console applications and explicit entry points (Microsoft Learn)
- Notes and guidance on FSharp.Core (The F# compiler team (dotnet/fsharp))
Report a problem with this lesson
Kept only in this browser. Your Learn progress