Skip to content

[API Proposal]: Add a "You can ignore the return value safely" attribute #61382

Open
@cartermp

Description

@cartermp

Background and motivation

There are a lot of .NET APIs out there that return a value for the purposes of fluent APIs. However, in many cases (probably most?) these values are just ignored and people call APIs imperatively. Two examples of this are ASP.NET Core configuration and OpenTelemetry APIs.

This presents an awkward situation for F#, which like the Go language, doesn't support implicit ignoring of values. The reason is that it can often be a bug when something is implicitly discarded. It's baked into the language and can't really be removed.

Here's a code sample in F# where you can use the new "minimal" APIs to build an instrumented web service with OpenTelemetry:

//.. some 'open' declarations elided
let builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs())

builder.Services.AddHoneycomb(builder.Configuration) |> ignore // 1

let rootHandler (tracer: Tracer) =
    task {
        use span = tracer.StartActiveSpan("sleep span")
        span.SetAttribute("duration_ms", 100) |> ignore // 2

        do! Task.Delay(100)

        return "Hello World!"
    }

let app = builder.Build()
app.MapGet("/", Func<Tracer, Task<string>>(rootHandler)) |> ignore // 3

app.Run()

3 ignore calls is annoying, but this is actually pretty good compared to "real world" code with lots of routes, lots of instrumentation calls, and lots of wiring up to some other system/API.

The F# compiler is unlikely to be taught to know about every API out there that has a "fluent-like" API where the usage patterns aren't typically fluent in nature.

Instead, an API in the runtime that library authors can sprinkle around a bit could be better. There is precedent for this already, where the F# compiler will respect several attributes (some defined in the runtime, some in FSharp.Core) and either emit or not emit a diagnostic based on the presence of that attribute.

With an attribute, API authors can decorate methods they don't expect to be used in a fluent/builder-like fashion, and the F# compiler will not emit a diagnostic, and thus F# programmers can remove a bunch of explicit ignore calls.

API Proposal

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.ReturnValue)]
    public class SafeToIgnore : Atttribute
    {
    }
}

API Usage

using System.Runtime.CompilerServices;
// ... some code in OpenTelemetry .NET, Span.cs

[return: SafeToIgnore]
public Span SetAttribute(string key, string value)
{
    //...
}

Alternative Designs

There are several alternatives I can think of:

  • Call it SafeToIgnoreReturnValueAttribute - basically the same proposal, just a different name.
  • Define this attribute in FSharp.Core - The biggest positive here is that it's easy to land, scoped only to F#, and coming from a source you can trust is "doing it right" (FSharp.Core). The biggest downside is that the FSharp.Core package must be added as a dependency to many .NET projects despite not having any F# code in their projects. It's a rather large binary too (4MB) that I'm sure most package maintainers wouldn't want to include just for the sake of a single attribute.
  • Define this attribute in some other package - The biggest positive is that this is the easiest option, since it's independent of everything, portable everywhere, and small. The biggest downside is that it's just some random package. If Microsoft were to build and publish it, that could help a problem related to credibility.
  • Make the compiler know about some APIs - The biggest positive is there's no API considerations for anyone. The compiler just "magically" knows about some common APIs like ASP.NET Core and OpenTelemetry. The downside is that this is probably never going to happen.
  • Don't warn on implicitly discarded values in F# anymore - This is not going to happen.
  • Do nothing - This the status quo.

Risks

I can't imagine this is much of a lift from an API design and implementation standpoint.

The bigger work would be in the F# compiler. It's relatively straightforward work to do though.

Putting this attribute in the runtime also means that it won't be available in a portable manner. So this may disallow OpenTelemetry (one of my examples) from using the attribute. It would be available in ASP.NET Core though, which is where a lot of this annoyance comes from today.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions