# Migrating to 0.21 Version 0.21 makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed accordingly. This is a guide for migrating to 0.21. For a full list of changes in this release, please see the [changelog](../CHANGELOG.md). Most of these changes are needed to ensure that all local references (`JObject` and the like) have the correct lifetime and can't be used after they're deleted, which would cause undefined behavior. See [issue #392](https://github.com/jni-rs/jni-rs/issues/392) for a discussion of the problem these changes solve. ## `JNIEnv` parameter of `extern fn`s should now be `mut` In `extern fn`s that are directly called by the JVM, the `JNIEnv` parameter will usually need to be `mut`. ```rust #[no_mangle] pub extern "system" fn Java_HelloWorld_hello<'local>(mut env: JNIEnv<'local>, class: JClass<'local>, input: JString<'local>) -> jstring { … } ``` This is needed because most `JNIEnv` methods now take a `&mut self` parameter. ## `JNIEnv`, `JObject`, etc are now `!Copy` and should be borrowed Functions that are *not* directly called by the JVM should, in most cases, borrow local references (`JObject`, `JString`, and so on) and mutably borrow `JNIEnv`s. ```rust pub fn print_string(env: &mut JNIEnv, string: &JString) -> Result<()> { println!("{}", env.get_string(string)?.to_string_lossy()); Ok(()) } ``` This is needed because these types no longer have the `Copy` or `Clone` traits. ## `JNIEnv::with_local_frame` closure now takes a `&mut JNIEnv` parameter When using `JNIEnv::with_local_frame`, `Executor::with_attached`, or `Executor::with_attached_capacity`, the closure must now take a parameter of type `&mut JNIEnv`. ```rust env.with_local_frame(16, |env| { … }) ``` The closure must only use the `JNIEnv` passed to it in that parameter, and not the `JNIEnv` that `with_local_frame` was called on. ## `JNIEnv::with_local_frame` closure can return a generic `Result` _Note: This also applies to `Executor::with_attached` and `Executor::with_attached_capacity` which are thin wrappers over `with_local_frame`_ The closure passed to `with_local_frame` is now free to return a generic `Result` as long as the error type implements `From`. This can be particularly beneficial when running large amounts of code within a local frame in a crate that defines its own `Result` and `Error` types which need to be propagated to the caller. There are a few trade offs with this change though: 1. Sometimes the compiler won't be able to infer the generic error type (E.g. for code that simply returns `Ok(())`) and so it has to be explicitly specified 2. Since it's no longer assumed that code always wants to return a single local reference this special case has to be handled differently Two options for clarifying the error type for the compiler if it's ambiguous would be: 1. Specify the type of the return value as part of an assignment: ```rust let result: MyResult<()> = env.with_local_frame(10, |env| { Ok(()) }); ``` 2. Specify the generic `E` error parameter: ```rust env.with_local_frame::<_, _, MyError>(10, |env| { Ok(()) })?; ``` Code that returns a local reference to the calling frame can either use `JNIEnv::with_local_frame_returning_local` (which is marginally optimized for that special case) or else return a `GlobalRef` instead. (This approach works reasonably well in Rust, compared to C, because a global reference will be automatically deleted once it is dropped so it doesn't introduce a memory leak hazard like it would in C) ## Passing object reference parameters to Java methods When passing an object reference as a parameter to a Java method or constructor, it needs to be explicitly borrowed, as in `(&obj).into()`, instead of simply `obj.into()`. ```rust env.call_static_method( "com/example/SomeClass", "someStaticMethod", "(Ljava/lang/Object;)V", &[ (&obj).into(), ], ) ``` ## `JList` and `JMap` methods all take a `&mut JNIEnv` parameter All methods of `JList` and `JMap` now require a parameter of type `&mut JNIEnv`. They no longer store the `JNIEnv` that was used to construct them. This is needed because most `JNIEnv` methods now take a `&mut self` parameter, and if the `JList` or `JMap` did store a `&mut JNIEnv`, then the caller would be unable to use the `JNIEnv` for anything else without first dropping the `JList` or `JMap`. ## `JList` and `JMap` iterators no longer implement `Iterator` Just like the methods of `JList` and `JMap`, their iterator now also requires a `&mut JNIEnv` in order to get the next element. For this reason, the iterator returned by `JList::iter` and `JMap::iter` no longer implements `std::iter::Iterator` and can no longer be used with a `for` loop. Instead, it has a `next` method that takes a `&mut JNIEnv` parameter, and can be used with a `while let` loop. ```rust let mut iterator = list.iter(env)?; while let Some(obj) = iterator.next(env)? { let obj: AutoLocal = env.auto_local(obj); // Do something with `obj` here. } ``` ## Local references no longer leak `JNIEnv` methods that look up Java classes no longer leak local references ([#109](https://github.com/jni-rs/jni-rs/issues/109)), so it is no longer necessary to wrap calls to these methods inside `JNIEnv::with_local_frame`. ## `cannot borrow as mutable` If your project has a function that takes two or more parameters, one of them is `JNIEnv`, and another is something returned by a `JNIEnv` method (like `JObject`), then calls to that function may not compile. ```rust fn example_function( env: &mut JNIEnv, obj: &JObject, ) { // … } example_function( env, // ERROR: cannot borrow `*env` as mutable more than once at a time &env.new_object( "com/example/SomeClass", "()V", &[], )?, ) ``` To fix this, the `JNIEnv` parameter needs to come *last*. ```rust fn example_function( obj: &JObject, env: &mut JNIEnv, ) { // … } example_function( &env.new_object( "com/example/SomeClass", "()V", &[], )?, env, ); ``` ## `invocation` feature now finds and loads the JVM dynamically; `JavaVM::new` returns different error The `invocation` feature has been changed to locate and load the Java Virtual Machine at run time, using the [java-locator](https://crates.io/crates/java-locator) and [libloading](https://crates.io/crates/libloading) libraries. `JavaVM::new` now returns a different error type, `StartJvmError`, when it fails. This new error type covers failures to locate and load the JVM library as well as failures to initialize the JVM. If you need to load a specific JVM library instead of automatically discovering one, use `JavaVM::with_libjvm` instead. On non-Windows platforms, it is no longer necessary for the Java runtime's `lib` folder to be on the shared library search path (`LD_LIBRARY_PATH` or equivalent). Unfortunately, this is *not* true on Windows, where the Java runtime's DLLs must still be on the `PATH` (or added to the DLL search path with [`SetDllDirectory`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw)). ## `Desc` has been redesigned The `Desc` trait has been redesigned. If your project contains any implementations of `Desc` and/or calls to `Desc::lookup`, they will need to be updated. If your project uses `Desc` directly like this, please post a comment on [issue #403](https://github.com/jni-rs/jni-rs/issues/403) explaining your situation. We view `Desc` as an implementation detail, and are considering sealing it and hiding its contents in a future release. Changes to `Desc` in this release are as follows: - `Desc` is now an `unsafe trait`. The safety requirements haven't actually changed, but they were undocumented until now. - `Desc` now has an associated type, `Output`, which is now the type returned by the `lookup` method. Please see the documentation for the `Desc` trait for more information. ## `JNIEnv::call_*method_unchecked` is now `unsafe` The `JNIEnv::call_*method_unchecked` methods are now `unsafe`. Their safety requirements haven't changed, but they were undocumented until now. ## `AutoLocal` type and lifetime parameters changed `AutoLocal` now has one lifetime parameter, representing the local reference frame it belongs to, and one type parameter, representing the type of object reference it contains (`JObject`, `JClass`, and so on). This means that the object reference stored in an `AutoLocal` no longer has its type erased. If it was a `JClass` before wrapping it in `AutoLocal`, it will still be a `JClass` after. ```rust // `AutoLocal` type parameters now include the type of object reference… let auto_local: AutoLocal<'local, JClass<'local>>; // …so the type of the object reference is now kept instead of being erased. let local: &JClass<'local> = &*auto_local; ``` ## `JObject` no longer implements `From<&AutoLocal> + From<&GlobalRef>` It is no longer possible to directly convert an `&AutoLocal` or `&GlobalRef` into a `JObject`. Instead, a `JObject` can be *borrowed* from `AutoLocal` or `GlobalRef` through their implementations of `Deref` and/or `AsRef`. ```rust let global_ref: GlobalRef; // You can get a `JObject` from a `GlobalRef` two ways: let object_ref: &JObject<'static> = &*global_ref; let object_ref: &JObject<'static> = global_ref.as_ref(); ``` ## `JNIEnv::get_superclass` now returns `Option` The `JNIEnv::get_superclass` method previously returned a `JClass`, which would be null if the class in question doesn't have a superclass. It now returns `Option` instead, and when it's `Some`, the `JClass` inside is never null. ## `JNIEnv::{get,release}_string_utf_chars` removed The methods `JNIEnv::get_string_utf_chars` and `JNIEnv::release_string_utf_chars` have been removed. These methods have been replaced with `JavaStr::into_raw` and `JavaStr::from_raw`. To get a `JavaStr`, use `JNIEnv::get_string` or `JNIEnv::get_string_unchecked`. See [issue #372](https://github.com/jni-rs/jni-rs/pull/372) for discussion. ## `JPrimitiveArray` and `JObjectArray` provide safe reference wrappers (like `JObject`) for `jarray` This affects all `JNIEnv` array APIs including: - `new__array` - `get_array_length` - `get_object_array_element` - `set_object_array_element` - `get__array_region` - `set__array_region` - `get_array_elements` - `get__array_elements` (all removed) - `get_primitive_array_elements` (also renamed to `get_array_elements_critical`) With the exception of `get_array_length` these APIs now take or return a reference to a `JPrimitiveArray` or a `JObjectArray` instead of a `sys` type like `jarray` or `jbyteArray`. This improves safety since the sys types can be copied and easily read as invalid pointers after a reference has been deleted. `get_array_length` will accept a reference to a `JPrimitiveArray` or a `JObjectArray` since these both implement the `AsJArrayRaw` trait. You can also query the `.len()` of an array via the `AutoElements`/`AutoElementsCritical` APIs if you use those. There are the following type-specific aliases for `JPrimitiveArray`: - `JBooleanArray` - `JByteArray` - `JCharArray` - `JShortArray` - `JIntArray` - `JLongArray` - `JFloatArray` - `JDoubleArray` ## `AutoArray` and `AutoPrimitiveArray` renamed to `AutoElements` and `AutoElementsCritical` respectively This rename was done to: 1. Clarify the connection between these APIs (they both provide temporary access to the elements of an array) 2. Clarify their differences (`AutoElementsCritical` is an alternative that defines a restricted "critical" section that helps JNI avoid the need to copy the data for the array) 3. Differentiate these from the new `JPrimitiveArray`/`JObjectArray` types and aliases like `JByteArray` that represent the array itself (not the elements) ## `AutoElements` and `AutoElementsCritical` now implement `Deref` and `DerefMut` Previously accessing array elements required the use of `unsafe` code to access array elements via `AutoArray::as_ptr()` after acquiring an `AutoArray` guard. It's now possible to read and write elements via the `Deref` and `DerefMut` traits that will deref into a `[T]` slice like: ```rust let byte_array = env.new_byte_array(100)?; { let mut elements = unsafe { env.get_array_elements(&byte_array, ReleaseMode::CopyBack) }?; elements[0] = 0xff; assert_eq!(elements[0], 0xff); // elements released (copied back to Java) here on Drop } ``` If you are accessing a `JPrimitiveArray`/`JByteArray` you may find it helpful to utilize the [bytemuck](https://crates.io/crates/bytemuck) crate in case you need to access the elements as `u8` unsigned bytes instead of `i8`. Although this hasn't changed, it seems worth highlighting that if you are accessing a `JPrimitiveArray`/`JBooleanArray` you are responsible for only storing values of `0` or `1`, since other values could lead to undefined behaviour for the JVM. ## `AutoArray/AutoPrimitiveArray::size()` replace by `AutoElements/AutoElementsCritical::len()` Previously `AutoArray::size()` was a wrapper for calling `JNIEnv::get_array_length()` which could fail, where as `AutoElements` and `AutoElementsCritical` now need to know the array length to be constructed, and this constant is accessible via the `.len()` method. This change was required to support being able to `Deref` to a `[T]` slice. ## `get_array_elements[_critical]` are `unsafe` Previously `get_array_elements` and `get_primitive_array_critical` could be called by safe code but there were multiple ways in which these APIs could quite easily lead to undefined behaviour. Since it is only possible to acquire an `AutoElements` and `AutoElementsCritical` instance via `get_array_elements` and `get_primitive_array_critical`, that's where we now document the safety rules that need to be followed to avoid any undefined behaviour.