"use!" in a computation expression

I use this great library: GitHub - demystifyfp/FsToolkit.ErrorHandling: An opinionated F# Library for error handling - for data validation and error handling.

I have the following snippet of code:

type ConversionErrorType2 = ResizeArray<string>

let openSqlConnection (settings: Settings): Result<SqlConnection, ConversionErrorType2> =
    try
        Ok (createAndOpenConnection settings.DestServerName settings.DestDbName)
    with
        | ex ->
           Error ([genericAppErrorMsg ex] |> ResizeArray)
....
     let processResult = result {
         use! cn = openSqlConnection settings
         return! getImportColumns cn settings
                 >>= parseCsv settings
                 >>= importData cn settings.DestSqlTableName 
     }

result is a computation expression defined in the library. I didn’t include all the code but that is not important.

My question is related to use!. I had to load about 700 csv files and eventually the connection pool ran out of connections which means that use! doesn’t do what I thought it should do, i.e. release the connection when the computation block is done.

I looked here: Computation Expressions - F# | Microsoft Learn and use! is not even mentioned, though the compiler accepted it.

I ended up removing it from the block and placing up before the result CE.

Any thoughts about what is going on with use!? Does it actually get translated to let!?

Thanks

This documentation might be of some use: Resource Management: The use Keyword - F#

It says that: “It [the use keyword] provides the same functionality as a let binding but adds a call to Dispose on the value when the value goes out of scope.”

See also: Binding with let, use, and do | F# for fun and profit

Unfortunately I have no idea if there is some kind of internal maximum number for disposables and/or file connections. (I’ve posted this more as a jumping-off point for people who come here wondering what use does.)

I read the docs about use & using. The problem is the use! binding. It doesn’t seem to call the Dispose method of the connection.

Ok, I used the decompiler and the connection is disposed. I must have been doing something else wrong :grimacing:

Just to show it:

let test2() = 
    let res = result {
        use! cn = createCn2()
        return! cn |> openCn >>= runQuery
    }
    res

The decompiled code is:

test2 ()
   ResultCE.ResultBuilder result = ResultCE.result;
   ResultCE.ResultBuilder resultBuilder = result;
   ResultCE.ResultBuilder resultBuilder2 = result;
   ResultCE.ResultBuilder resultBuilder3 = result;
   ResultCE.ResultBuilder resultBuilder4 = result;
   FSharpResult<SqlConnection, string> fSharpResult = createCn2 ();
   FSharpResult<DateTime, string> result2;
   if (fSharpResult.Tag != 1)
   {
        SqlConnection resultValue = fSharpResult.ResultValue;
        SqlConnection cn = resultValue;
        ResultCE.ResultBuilder resultBuilder5 = result;
        SqlConnection sqlConnection = cn;
        FSharpFunc<SqlConnection, FSharpResult<DateTime, string>> fSharpFunc = new res@72 (result);
        FSharpResult<DateTime, string> fSharpResult2;
        try
        {
             fSharpResult2 = fSharpFunc.Invoke (sqlConnection);
        }
        finally
        {
             if (!object.ReferenceEquals (sqlConnection, null))
             {
                  SqlConnection sqlConnection2 = sqlConnection;
                  ((IDisposable)(SqlConnection)(&sqlConnection2)).Dispose ();
             }
        }
        result2 = fSharpResult2;
   }
   else
   {
        result2 = FSharpResult<DateTime, string>.NewError (fSharpResult.ErrorValue);
   }
   return result2;

res@72.Invoke (SqlConnection)
   ResultCE.ResultBuilder resultBuilder = builder@;
   ResultCE.ResultBuilder resultBuilder2 = builder@;
   FSharpResult<SqlConnection, string> fSharpResult = openCn (_arg2);
   if (fSharpResult.Tag != 1)
   {
        return runQuery (fSharpResult.ResultValue);
   }
   return FSharpResult<DateTime, string>.NewError (fSharpResult.ErrorValue);

Though I find this expression a bit odd: ((IDisposable)(SqlConnection)(&sqlConnection2)).Dispose ();.

This:

let test1() =
    use cn = createCn1()
    cn.Open()

gets compiled to:

test1 ()
   SqlConnection cn = createCn1 ();
   try
   {
        cn.Open ();
   }
   finally
   {
        IDisposable disposable = cn as IDisposable;
        if (disposable != null)
        {
             disposable.Dispose ();
        }
   }