Skip to content

Commit b7610d7

Browse files
committed
EnvResult supporting map_err with Env reference
This provides one possible solution for being able to map errors to exceptions in native method implementations, after using `EnvUnowned::with_env` What I don't particularly like is that you would probably end up repeating a bunch of boilerplate across lots of different native methods and it would be nice if you could instead implement a trait for your own `Error` type that could e.g. have a `to_throwable` or `map_err` method.
1 parent 944059e commit b7610d7

File tree

1 file changed

+114
-8
lines changed

1 file changed

+114
-8
lines changed

src/env.rs

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3856,6 +3856,95 @@ impl<'local> Env<'local> {
38563856
}
38573857
}
38583858

3859+
/// An opaque wrapper around a `Result<T, Error>` that supports mapping errors
3860+
/// with access to an [`Env`] reference.
3861+
///
3862+
/// This returned by [`EnvUnowned::with_env`] and is designed for use within
3863+
/// native method implementations where unwinding can't be caught by the JVM and
3864+
/// will abort the process.
3865+
///
3866+
/// There is no `.unwrap()` that would panic on error but, if necessary, you can
3867+
/// use [Self::unwrap_result] to get the inner `Result` and handle the error as
3868+
/// you see fit.
3869+
///
3870+
/// In most cases it is recommended to either:
3871+
/// 1. Use [Self::map_err] to convert errors to an Ok value that can be returned
3872+
/// from your native method or to throw an exception using the provided `Env`
3873+
/// reference.
3874+
/// 2. Use [Self::unwrap_or_default] to return a default value (often `null` for
3875+
/// reference types) in case of an error, considering that `null` is often a
3876+
/// valid return value for Java methods.
3877+
#[must_use = "the inner `Result` may be an `Err` variant, which should be handled. `map_err` has access to JNI"]
3878+
#[derive(Debug)]
3879+
pub struct EnvResult<'unowned_frame, T, E>
3880+
where
3881+
E: From<Error>,
3882+
{
3883+
env_ptr: *mut jni_sys::JNIEnv,
3884+
result: std::result::Result<T, E>,
3885+
_lifetime: std::marker::PhantomData<&'unowned_frame ()>,
3886+
}
3887+
3888+
impl<'unowned_frame, T, E> EnvResult<'unowned_frame, T, E>
3889+
where
3890+
E: From<Error>,
3891+
{
3892+
/// Creates a new [`EnvResult`] from a raw JNI environment pointer and a
3893+
/// `Result`.
3894+
///
3895+
/// # Safety
3896+
///
3897+
/// The `ptr` must be a valid, non-null pointer to a `JNIEnv`.
3898+
pub(crate) unsafe fn new(
3899+
env_ptr: *mut jni_sys::JNIEnv,
3900+
result: std::result::Result<T, E>,
3901+
) -> Self {
3902+
Self {
3903+
env_ptr,
3904+
result,
3905+
_lifetime: std::marker::PhantomData,
3906+
}
3907+
}
3908+
3909+
/// Maps the error value of this [`EnvResult`] to another type using the
3910+
/// provided closure.
3911+
///
3912+
/// The closure is provided with a mutable reference to an `Env` that can
3913+
/// access JNI and be used to throw exceptions if needed.
3914+
///
3915+
/// The closure effectively runs within a call to [`EnvUnowned::with_env`]
3916+
/// that will catch any panics and return an [`Error::PanicCaught`] if a
3917+
/// panic occurs.
3918+
pub fn map_err<F, O>(self, f: F) -> EnvResult<'unowned_frame, T, O>
3919+
where
3920+
F: FnOnce(&mut Env<'unowned_frame>, E) -> O,
3921+
O: From<Error>,
3922+
{
3923+
let mut unowned_env: EnvUnowned<'unowned_frame> =
3924+
unsafe { EnvUnowned::from_raw(self.env_ptr) };
3925+
unowned_env.with_env(|env| self.result.map_err(|e| f(env, e)))
3926+
}
3927+
3928+
/// Consumes the `SafeResult`, returning the wrapped `Result`
3929+
///
3930+
/// Be careful within the implementation of native methods to not `.unwrap()`
3931+
/// or `.expect()` the `Result` and potentially unwind across the FFI boundary.
3932+
pub fn unwrap_result(self) -> std::result::Result<T, E> {
3933+
self.result
3934+
}
3935+
3936+
/// Consumes the [`EnvResult`], returning the wrapped `Ok` value or `T::default()`.
3937+
///
3938+
/// Note that all [`Reference`] types, like [`JObject`] or [`JString`] etc implement
3939+
/// `Default`, which will return a `null` which is often valid for returning to Java.
3940+
pub fn unwrap_or_default(self) -> T
3941+
where
3942+
T: Default,
3943+
{
3944+
self.result.unwrap_or_default()
3945+
}
3946+
}
3947+
38593948
/// Represents an external (unowned) JNI stack frame and thread attachment that
38603949
/// was passed to a native method call.
38613950
///
@@ -3896,22 +3985,28 @@ impl<'unowned_frame> EnvUnowned<'unowned_frame> {
38963985
/// method implementations in cases where you have named the lifetime for
38973986
/// the caller's JNI stack frame.
38983987
///
3899-
/// Since it would lead to undefined behaviour to allow Rust code to unwind
3900-
/// across a native method call boundary, this API wraps the closure
3901-
/// in a [`catch_unwind`] to catch any panics and return an [`Error::PanicCaught`]
3902-
/// if a panic occurs.
3988+
/// It returns an [`EnvResult`] that is an opaque wrapper around your
3989+
/// `Result` that continues to borrow your `EnvUnowned` reference to support
3990+
/// mapping errors with access to an [`Env`], so you may choose to throw
3991+
/// errors as exceptions.
3992+
///
3993+
/// To avoid the risk of unwinding into the JVM (which will abort the
3994+
/// process) this API wraps the closure in a [`catch_unwind`] to catch any
3995+
/// panics and return an [`Error::PanicCaught`] if a panic occurs.
39033996
///
39043997
/// Note: This API does not create a new JNI stack frame, which is normally
39053998
/// what you want when implementing a native method, since the JVM will
39063999
/// clean up the JNI stack frame when the native method returns.
3907-
pub fn with_env<F, T, E>(&mut self, f: F) -> std::result::Result<T, E>
4000+
///
4001+
/// Note: This API returns an [`EnvResult`]
4002+
pub fn with_env<F, T, E>(&mut self, f: F) -> EnvResult<'unowned_frame, T, E>
39084003
where
39094004
F: FnOnce(&mut Env<'unowned_frame>) -> std::result::Result<T, E>,
39104005
E: From<Error>,
39114006
{
39124007
// Safety: we trust that self.ptr a valid, non-null pointer
39134008
let mut guard = unsafe { AttachGuard::from_unowned(self.ptr) };
3914-
guard.with_env_current_frame(|env| {
4009+
let result = guard.with_env_current_frame(|env| {
39154010
// ☠☠☠☠☠☠☠☠☠☠☠☠☠☠ !WARNING! ☠☠☠☠☠☠☠☠☠☠☠☠☠☠
39164011
//
39174012
// We are casting the Env<'lifetime> here so the closure will
@@ -3955,7 +4050,8 @@ impl<'unowned_frame> EnvUnowned<'unowned_frame> {
39554050
Ok(ret) => ret,
39564051
Err(payload) => Err(Error::PanicCaught(panic_payload_to_string(payload)).into()),
39574052
}
3958-
})
4053+
});
4054+
unsafe { EnvResult::new(self.ptr, result) }
39594055
}
39604056

39614057
/// Runs a closure with a [`Env`] based on an unowned JNI thread attachment
@@ -4006,7 +4102,7 @@ impl<'unowned_frame> EnvUnowned<'unowned_frame> {
40064102
/// know represents a valid JNI attachment for the current thread.
40074103
///
40084104
/// If you are implementing a native method in Rust though, you should
4009-
/// prefer to use the `EnvUnowned` type as the first argument to your
4105+
/// prefer to use the [`EnvUnowned`] type as the first argument to your
40104106
/// native method and avoid the need to use a raw pointer.
40114107
///
40124108
/// If you have a raw [`crate::sys::JNIEnv`] pointer, this API should be
@@ -4035,6 +4131,16 @@ impl<'unowned_frame> EnvUnowned<'unowned_frame> {
40354131
_lifetime: std::marker::PhantomData,
40364132
}
40374133
}
4134+
4135+
/// Returns the raw pointer to the underlying [`crate::sys::JNIEnv`].
4136+
pub fn as_raw(&self) -> *mut jni_sys::JNIEnv {
4137+
self.ptr
4138+
}
4139+
4140+
/// Consumes the [`EnvUnowned`] and returns the raw pointer to the underlying [`crate::sys::JNIEnv`].
4141+
pub fn into_raw(self) -> *mut jni_sys::JNIEnv {
4142+
self.ptr
4143+
}
40384144
}
40394145

40404146
#[derive(Debug)]

0 commit comments

Comments
 (0)