Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 1 | // Copyright 2022 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | // This example serves to demonstrate the experimental C++ |
| 10 | // reference wrappers. They exist because C++ references are not |
| 11 | // the same as Rust references: C++ references may alias, whereas |
| 12 | // Rust references may not. |
| 13 | // |
| 14 | // Standard autocxx behavior therefore introduces unsoundness when |
| 15 | // C++ references are encountered and treated like Rust references. |
| 16 | // (cxx has this soundness problem for Trivial types; autocxx |
| 17 | // makes it worse in that the same problem applies even for |
| 18 | // opaque types, because we make them sized such that we can allocate |
| 19 | // them on the stack). |
| 20 | // |
| 21 | // Reference wrappers solve that problem because internally, they're |
| 22 | // just pointers. On the other hand, they're awkward to use, |
| 23 | // especially in the absence of the Rust "arbitrary self types" |
| 24 | // feature. |
| 25 | |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 26 | // Necessary to be able to call methods on reference wrappers. |
| 27 | // For that reason, this example only builds on nightly Rust. |
| 28 | #![feature(arbitrary_self_types)] |
| 29 | |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 30 | use autocxx::prelude::*; |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 31 | use std::pin::Pin; |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 32 | |
| 33 | include_cpp! { |
| 34 | #include "input.h" |
| 35 | // This next line enables C++ reference wrappers |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 36 | // This is what requires the 'arbitrary_self_types' feature. |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 37 | safety!(unsafe_references_wrapped) |
| 38 | generate!("Goat") |
| 39 | generate!("Field") |
| 40 | } |
| 41 | |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 42 | impl ffi::Goat { |
| 43 | // Methods can be called on a CppRef<T> or &CppRef<T> |
| 44 | fn bleat(self: CppRef<Self>) { |
| 45 | println!("Bleat"); |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | trait FarmProduce { |
| 50 | // Traits can be defined on a CppRef<T> so long as Self: Sized |
| 51 | fn sell(self: &CppRef<Self>) |
| 52 | where |
| 53 | Self: Sized; |
| 54 | } |
| 55 | |
| 56 | impl FarmProduce for ffi::Goat { |
| 57 | fn sell(self: &CppRef<Self>) { |
| 58 | println!("Selling goat"); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | trait FarmArea { |
| 63 | fn maintain(self: CppRef<Self>); |
| 64 | } |
| 65 | |
| 66 | impl FarmArea for ffi::Field { |
| 67 | fn maintain(self: CppRef<Self>) { |
| 68 | println!("Maintaining"); |
| 69 | } |
| 70 | } |
| 71 | |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 72 | fn main() { |
| 73 | // Create a cxx::UniquePtr as normal for a Field object. |
| 74 | let field = ffi::Field::new().within_unique_ptr(); |
| 75 | // We assume at this point that C++ has had no opportunity |
| 76 | // to retain any reference to the Field. That's not strictly |
| 77 | // true, due to RVO, but under all reasonable circumstances |
| 78 | // Rust currently has exclusive ownership of the Field we've |
| 79 | // been given. |
| 80 | // Therefore, at this point in the program, it's still |
| 81 | // OK to take Rust references to this Field. |
| 82 | let _field_rust_ref = field.as_ref(); |
| 83 | // However, as soon as we want to pass a reference to the field |
| 84 | // back to C++, we have to ensure we have no Rust references |
| 85 | // in existence. So: we imprison the object in a "CppPin": |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 86 | let field = CppUniquePtrPin::new(field); |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 87 | // We can no longer take Rust references to the field... |
| 88 | // let _field_rust_ref = field.as_ref(); |
| 89 | // However, we can take C++ references. And use such references |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 90 | // to call methods in C++. Quite often those methods will |
| 91 | // return other references, like this. |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 92 | let another_goat = field.as_cpp_ref().get_goat(); |
| 93 | // The 'get_goat' method in C++ returns a reference, so this is |
| 94 | // another CppRef, not a Rust reference. |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 95 | |
| 96 | // We can still use these C++ references to call Rust methods, |
| 97 | // so long as those methods have a "self" type of a |
| 98 | // C++ reference not a Rust reference. |
| 99 | another_goat.clone().bleat(); |
| 100 | another_goat.sell(); |
| 101 | |
| 102 | // But most commonly, C++ references are simply used as the 'this' |
| 103 | // type when calling other C++ methods, like this. |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 104 | assert_eq!( |
| 105 | another_goat |
| 106 | .describe() // returns a UniquePtr<CxxString>, there |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 107 | // are no Rust or C++ references involved at this point. |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 108 | .as_ref() |
| 109 | .unwrap() |
| 110 | .to_string_lossy(), |
| 111 | "This goat has 0 horns." |
| 112 | ); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 113 | |
| 114 | // We can even use trait objects, though it's a bit of a fiddle. |
| 115 | let farm_area: Pin<Box<dyn FarmArea>> = ffi::Field::new().within_box(); |
| 116 | let farm_area = CppPin::from_pinned_box(farm_area); |
| 117 | farm_area.as_cpp_ref().maintain(); |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 118 | } |