Skip to content

F# Guide — File I/O: Reading and Writing Files

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

F# file I/O combines .NET's powerful file APIs with functional patterns like sequences, async, and resource management for safe, efficient file operations.

What You'll Learn

  • Reading files with sequences
  • Writing files safely
  • Async file operations
  • Directory and path operations
  • Working with different file formats

Why It Matters

File I/O is fundamental for data processing, logging, configuration, and storage. Durga Antivirus Pro uses file I/O for scanning log files and reading signature databases.

Real-World Use

Log file analysis, configuration reading, data export/import, and batch file processing.

flowchart LR
    A["File I/O"] --> B["Reading"]
    B --> C["Writing"]
    C --> D["Async"]
    D --> E["Directories"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Reading Files

open System.IO

// Read all at once
let content = File.ReadAllText("file.txt")

// Read lines lazily
let lines = File.ReadLines("file.txt")  // Returns seq<string>

// Process line by line (memory efficient for large files)
lines
|> Seq.filter (fun line -> line.Contains("ERROR"))
|> Seq.take 100
|> Seq.iter (printfn "%s")

Writing Files

open System.IO

// Write all at once
File.WriteAllText("output.txt", "Hello F#!")

// Write lines
File.WriteAllLines("lines.txt", [|"Line 1"; "Line 2"; "Line 3"|])

// Append
File.AppendAllText("log.txt", "New log entry\n")

Streaming

// Using streams for large files
let readLargeFile path =
    seq {
        use reader = new StreamReader(path)
        while not reader.EndOfStream do
            yield reader.ReadLine()
    }

// Write with StreamWriter
let writeLines path lines =
    use writer = new StreamWriter(path)
    for line in lines do
        writer.WriteLine(line)

Async File I/O

let readFileAsync path = async {
    use reader = new StreamReader(path)
    let! content = reader.ReadToEndAsync() |> Async.AwaitTask
    return content
}

let writeFileAsync path content = async {
    use writer = new StreamWriter(path)
    do! writer.WriteAsync(content) |> Async.AwaitTask
}

// Async line processing
let processFileAsync path = async {
    let! content = readFileAsync path
    return content.Split('\n')
           |> Array.filter (fun l -> l.Length > 0)
           |> Array.length
}

Directory Operations

// List files
Directory.GetFiles(".")  // All files
Directory.GetFiles(".", "*.txt")  // Filter by pattern
Directory.GetDirectories(".")  // Subdirectories

// Create/delete
Directory.CreateDirectory("newdir")
Directory.Delete("olddir", recursive=true)

// Path operations
let path = Path.Combine("dir", "subdir", "file.txt")
let ext = Path.GetExtension("file.txt")
let name = Path.GetFileNameWithoutExtension("file.txt")

Working with Binary Files

// Read binary
let bytes = File.ReadAllBytes("image.jpg")

// Write binary
let newBytes = [| 0uy; 1uy; 2uy |]
File.WriteAllBytes("output.bin", newBytes)

// Stream binary
let readBinaryChunks path bufferSize = seq {
    use stream = File.OpenRead(path)
    let buffer = Array.zeroCreate bufferSize
    let mutable bytesRead = stream.Read(buffer, 0, bufferSize)
    while bytesRead > 0 do
        yield buffer.[0..bytesRead-1]
        bytesRead <- stream.Read(buffer, 0, bufferSize)
}

Safe Resource Management

// use keyword disposes automatically
let countLines path =
    use reader = File.OpenText(path)
    let mutable count = 0
    while not reader.EndOfStream do
        reader.ReadLine() |> ignore
        count <- count + 1
    count

// use! for async disposal
let readWithLock path = async {
    use! stream = async {
        return File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)
    }
    use reader = new StreamReader(stream)
    let! content = reader.ReadToEndAsync() |> Async.AwaitTask
    return content
}

Common Mistakes

1. Forgetting to dispose

Always use use or using to close files. Leaked handles prevent other processes from accessing files.

2. Not handling exceptions

File operations throw exceptions. Use try-with for FileNotFoundException, IOException, UnauthorizedAccessException.

3. Loading entire file into memory

Use File.ReadLines (lazy) instead of File.ReadAllLines (eager) for large files.

4. Encoding issues

Specify encoding explicitly: File.ReadAllText(path, Encoding.UTF8).

5. Unsafe path concatenation

Use Path.Combine instead of string concatenation for paths.

Practice Questions

1. Why use File.ReadLines instead of File.ReadAllLines? File.ReadLines returns a lazy sequence, reading and yielding one line at a time without loading the entire file.

2. What does the use keyword do? It calls Dispose on the resource when it goes out of scope, ensuring proper cleanup even on exceptions.

3. How do you handle file not found errors? Use try-with: try ... with :? FileNotFoundException as ex -> ....

Challenge: Write a function that efficiently processes a large log file and counts occurrences of each log level.

FAQ

{{< faq question="Is F# file I/O slower than C#?" >}} No. F# uses the same .NET APIs under the hood. Performance is identical to C# file I/O. {{< /faq >}}

{{< faq question="Can I read a file from a URL?" >}} Yes. Use System.Net.WebClient, HttpClient, or the FSharp.Data HTTP utilities. {{< /faq >}}

{{< faq question="How do I check if a file exists?" >}} Use File.Exists(path) which returns a boolean. {{< /faq >}}

{{< faq question="What is the best way to write a log file?" >} Use a logging library (Serilog, NLog) or append with File.AppendAllText. Consider async for high throughput. {{< /faq >}}

{{< faq question="Can I use memory-mapped files?" >}} Yes. .NET provides MemoryMappedFile class which can be used from F#. {{< /faq >}}

Mini Project

Build a file search utility with async processing:

let searchFiles rootDir pattern keyword =
    Directory.EnumerateFiles(rootDir, pattern, SearchOption.AllDirectories)
    |> Seq.map (fun path -> async {
        let! lines = File.ReadAllLines(path) |> async.Return
        let matches = lines |> Array.filter (fun l -> l.Contains(keyword))
        return (path, matches.Length, matches)
    })
    |> Async.Parallel
    |> Async.RunSynchronously
    |> Array.filter (fun (_, count, _) -> count > 0)

// Usage
let results = searchFiles "/var/log" "*.log" "ERROR"
for (path, count, _) in results do
    printfn "%s: %d matches" path count

What's Next

Now that you understand file I/O, explore JSON processing in F#.

Topic Description Link
F# JSON JSON processing {{< ref "24-json" >}}
F# HTTP HTTP clients {{< ref "25-http" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro