Draft JEP: Enhanced Void return

Remi Forax forax at univ-mlv.fr
Mon Mar 31 06:43:36 UTC 2025


yes,
during the expert group discussions about lambdas, we explicitly rule out this case.

there is no boxing rules between Void and void.

we may revisit this idea in the future because at some point of Valhalla we will want to see primitive types as kind of wrapper/boxed value types, but we are not there yet.

regards,
Rémi

----- Original Message -----
> From: "Stephan Herrmann" <stephan.herrmann at berlin.de>
> To: "compiler-dev" <compiler-dev at openjdk.org>
> Sent: Thursday, March 27, 2025 9:22:13 PM
> Subject: Re: Draft JEP: Enhanced Void return

> How would this interact with definitions void-compatible / value-compatible in
> JLS §15.27.2.?
> 
> It seems all lambdas formerly classified as void-compatible would need to be
> re-classified as "potentially value-compatible" with an elided "return null;".
> This could entail a hole new can of worms for resolution / type inference.
> 
> BR
> Stephan
> 
>> Date: Thu, 27 Mar 2025 12:16:18 -0700
>> From: Brandon Mintern <mintern at everlaw.com>
>> To: compiler-dev at openjdk.org
>> Subject: Re: Draft JEP: Enhanced Void return
>> Message-ID:
>> 	<CAJW1onhzJX6yFnxM94SwpWoMVV1H0a35_1cxQp6B5qNui7ibWw at mail.gmail.com>
>> Content-Type: text/plain; charset="utf-8"
>> 
>> I'm sorry. I see that my prior email wraps poorly when rendered in the web
>> interface. Here's the same email wrapped to 76 characters for better
>> readability:
>> 
>> Title: Enhanced Void return
>> Author: Brandon Mintern
>> Organization: Everlaw
>> Created: 2025/03/27
>> Type: Feature
>> State: Draft
>> Exposure: Open
>> Component: specification / language
>> Scope: SE
>> Template: 2.0
>> 
>> Summary
>> -------
>> 
>> Allow `Void`-returning methods to omit the `return null` statement. Method
>> implementations can still `return null` if desired, but they can also use a
>> plain `return` or omit the statement altogether.
>> 
>> Goals
>> -----
>> 
>> Improve ergonomics when using methods that accept a lambda and optionally
>> return a result. Enhance the compiler to:
>> 
>> - Automatically insert `return null` (or convert `return`) in
>>    `Void`-returning methods.
>> - Infer `Void` as the generic return type in method references and lambdas
>>    without a `return`.
>> 
>> Non-Goals
>> ---------
>> 
>> - `Void`-returning methods are still free to explicitly state `return null`.
>> - The scope should be limited such that:
>>      - The compiler can do the heavy lifting, without any JVM changes.
>>      - No new lambda objects would be constructed implicitly.
>> 
>> Motivation
>> ----------
>> 
>> Lambdas provide powerful abstractions for working with closeable resources
>> or in temporary contexts. For example, we can define the following
>> abstraction:
>> 
>>      <R> R withBufferedReader(Path path, BufferedReaderFunction<R> f) {
>>          try (var reader = Files.newBufferedReader(path)) {
>>              return f.apply(reader);
>>          } catch (IOException e) {
>>              throw new UncheckedIOException(e);
>>          }
>>      }
>> 
>>      @FunctionalInterface
>>      interface BufferedReaderFunction<R> {
>>          R apply(BufferedReader reader) throws IOException;
>>      }
>> 
>> Callers can use this method as follows:
>> 
>>      var firstLine = withBufferedReader(path, r -> r.lines().findFirst());
>> 
>> When a caller doesn't need the return value, though, the caller must include
>> an awkward `return null`:
>> 
>>      withBufferedReader(path, reader -> {
>>          this.header = reader.readLine();
>>          this.data = reader.lines().toList();
>>          return null;
>>      });
>> 
>> These examples used `BufferedReader` for familiarity, but abstractions like
>> this are arguably more useful when reading from other persistent data stores
>> (e.g., databases, search indexes) or when writing context-specific code.
>> Using a completely hypothetical `UserContextHolder`:
>> 
>>      <R> R inUserContext(User user, Supplier<R> supplier) {
>>          var oldContext = UserContextHolder.getContext();
>>          UserContextHolder.setUserContext(user);
>>          try {
>>              return supplier.get();
>>          } finally {
>>              UserContextHolder.setContext(oldContext);
>>          }
>>      }
>> 
>> Then we might have system code that looks like the following:
>> 
>>      interface Report {
>>          User getUser();
>>          GeneratedReport generate();
>>      }
>> 
>>      for (var report: pendingReports) {
>>          save(inUserContext(report.getUser(), report::generate));
>>      }
>> 
>>      // or:
>> 
>>      interface Job {
>>          User getUser();
>>          void execute();
>>      }
>> 
>>      for (var job : pendingJobs) {
>>          inUserContext(job.getUser(), () -> {
>>              job.execute();
>>              return null;
>>          });
>>          // inUserContext(job.getUser(), job::execute) with this proposal
>>      }
>> 
>> Description
>> -----------
>> 
>> When a method has a fully instantiated return type of `Void`, all of the
>> following would be valid:
>> 
>> - `return null`
>> - `return`
>> - omitted `return` (implicit `void` return)
>> 
>> `Void`-returning methods can continue using `return null` without warning.
>> It should be at least a warning, though, for a single method to mix `return
>> null` and any `void`-style return (implicit or explicit).
>> 
>> When a functional interface has a generic return type parameter, lambdas can
>> use `void`-style returns, and references to `void`-returning methods can be
>> used, where the compiler will infer the return type to be `Void` and
>> logically insert the necessary `return null` statements.
>> 
>> As with functional interfaces in general, an instance of one type cannot be
>> automatically converted to another. That is, a `Runnable` cannot be passed
>> as a `Supplier<T>`, inferring `T = Void`. `Runnable::run` could be used
>> instead.
>> 
>> When a method returns a generic type, and when that type is inferred as
>> `Void` due to the use of an *enhanced `Void` return*, it would be an error
>> to assign the result to a variable. Returning to the `inUserContext`
>> example:
>> 
>>      var report = inUserContext(report.getUser(), report::generate); // OK
>> 
>>      Void result = inUserContext(job.getUser(), () -> {
>>          job.execute();
>>          return null;
>>      }); // OK: result is null (backwards compatible)
>> 
>>      Void result = inUserContext(job.getUser(), job::execute); // ERROR
>> 
>>      Void result = inUserContext(job.getUser(), () -> {
>>          job.execute();
>>      }); // ERROR
>> 
>> This enhancement would be implemented entirely by the compiler, generating
>> JVM bytecode as if `return null` had been used where it's currently needed.
>> 
>> Alternatives
>> ------------
>> 
>> Returning to the `withBufferedReader` example, without this proposal, we can
>> instead define sibling methods (and types) to avoid the need for
>> `return null`:
>> 
>>      void withBufferedReader(Path path, BufferedReaderProcedure f) {
>>          withBufferedReader(path, reader -> {
>>              f.accept(reader);
>>              return null;
>>          });
>>      }
>> 
>>      interface BufferedReaderProcedure {
>>          void accept(BufferedReader reader) throws IOException;
>>      }
>> 
>>      withBufferedReader(path, reader -> {
>>          this.header = reader.readLine();
>>          this.data = reader.lines().toList();
>>      });
>> 
>> This works, sort of, but we quickly find that it doesn't actually work.
>> Returning to the first example:
>> 
>>      var firstLine = withBufferedReader(path, r -> r.lines().findFirst());
>> 
>> This now fails to compile, reporting "Ambiguous method call." We can work
>> around that issue, of course, by standardizing on verb pairs to use in these
>> scenarios:
>> 
>>      R applyingBufferedReader(...)
>>      void acceptingBufferedReader(...)
>> 
>> In reality, though, this disambiguation is not the biggest issue. Rather, in
>> most codebases, it would be hard to justify defining duplicate methods and
>> types. Many development teams would instead use `return null` statements
>> where necessary, avoid the abstraction in cases where `return null` is
>> needed, or avoid defining the abstraction altogether.
>> 
>> Risks and Assumptions
>> ---------------------
>> 
>> Some codebases may have sibling methods like those described above, where
>> this enhancement would cause new "Ambiguous method call." when using the
>> `void`-returning method. However:
>> 
>> - Already-compiled code would not be affected since there is no JVM change.
>> - Impacted code would likely benefit from this enhancement, with new
>>    compiler errors pointing to duplicate code that can likely be deleted.
>> 


More information about the compiler-dev mailing list