Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I handle IDisposable's inside a Result in F#?

Tags:

f#

I want to return an HttpResponseMessage, which implements IDisposable, within a Result (or any other discriminated union). However, since Result itself is not IDisposable, I can't use! it like I would for the disposable object itself. What do I do? Can I implement my own DU called DisposableResult that implements IDisposable itself?

Below is an example of what I mean. crawlStuff asynchronously returns Result<HttpResponseMessage, string>. I can't use! the result, leading to a memory leak unless I manually release it.

open System.Net.Http

let crawlStuff (client : HttpClient) : Async<Result<HttpResponseMessage, string>> = 
    async {
        // res is HttpResponseMessage which implements IDisposable
        let! res = client.GetAsync("https://ifconfig.me") |> Async.AwaitTask
        return
            if res.IsSuccessStatusCode then
                Ok res
            else
                Error "Something wrong"
    } 

async {
    use client = new HttpClient()

    // Memory leak because result it could carry HttpResponseMessage.
    // I want to use! here, but can't because Result<> is not IDisposable
    let! response = crawlStuff client

    printfn "%A" response
} |> Async.RunSynchronously
like image 362
user3243135 Avatar asked Sep 17 '25 15:09

user3243135


1 Answers

I would've create wrapper around Result, that will dispose underlying values:

let (|AsDisposable|) x =
    match box x with
    | :? IDisposable as dis -> ValueSome dis
    | _ -> ValueNone

type ResultDisposer<'v, 'e> =
    struct
        val Result : Result<'v, 'e>
        new res = { Result = res }
        interface IDisposable with
            member r.Dispose() =
                match r.Result with
                // | Ok (:? IDisposable as dis) // causes FS0008, so we have to hack
                | Ok (AsDisposable (ValueSome dis))
                | Error (AsDisposable (ValueSome dis)) -> dis.Dispose()
                | _ -> ()
    end

type Result<'a, 'b> with
    member r.AsDisposable = new ResultDisposer<'a, 'b>(r)

And use it this way

async {
    use client = new HttpClient()
    let! response = crawlStuff client
    use _ = response.AsDisposable

    printfn "%A" response
} |> Async.RunSynchronously

This solution avoids need to rewrite existing code to DisposableResult and avoids allocations when disposable value is reference type, like in case of HttpResponseMessage. But decompilation shows that F# boxes ResultDisposer, even though it shouldn't :(

like image 78
JL0PD Avatar answered Sep 19 '25 04:09

JL0PD