Comments (7)
Yes, this is the "out parameter" / "out reference" pattern, which thus involve the optional support for them that safer-ffi
has:
-
Make sure the
out-refs
Cargo feature is enabled:[dependencies] safer-ffi.version = "…" safer-ffi.features = ["proc_macros", "out-refs"]
-
This will bring the
Out
type to::safer_ffi::prelude::Out
.This is a non-nullable pointer, so if it is optional, as per your:
Ideally I'd want the reference to be nullable, so that one could call
api_func(NULL)
in Cyou'll have to use
Option<Out<'_, …>>
types.
The main API to write through an Out
type is .write()
:
#[ffi_export]
fn api_func(error: Option<Out<'_, Error>>) {
if let Some(out_err) = err {
out_err.write(Error {
error_code: 0,
error_str: char_p::new("error"),
});
}
}
Remark regarding out_ref.write(…)
vs. *mut_ref = …;
Note that this overwrites the contents of the pointee, without handling / looking at the previous value (since it could be garbage, like it is idiomatic to do in C), which does have the caveat that you should not call .write()
multiple times in succession, lest you leak memory.
So, unless you .reborrow()
to chain multiple .write()
s, you'll otherwise find that .write()
consumes ownership from the Rust side, so that the following won't work:
out_err.write(Error {
error_code: 0,
error_str: char_p::new("error"),
});
// Error: use of moved value
out_err.write(Error {
error_code: 42,
error_str: char_p::new("let's nuance it"),
});
Should you need to be able to write that pattern, the trick is to use the return value of .write()
, which is a &mut _
this time (that is, a mutable reference to known-to-be-valid data (vs. Out<_>
, a "mutable reference" to potentially invalid/garbage data )):
/* initially: out_err: Out<'_, Error> */
let out_err = out_err.write(Error {
error_code: 0,
error_str: char_p::new("error"),
});
/* now: out_err: &'_ mut Error */
// OK
*out_err = Error {
error_code: 42,
error_str: char_p::new("let's nuance it"),
};
- (this btw explains the crash you were having: by treating the
Out<'_, Error>
you were receiving from the C/FFI side as a&mut Error
type, you were telling Rust that it was pointing to valid data, so that the*x = …;
was dropping the value previously there. And since that value had achar_p::Box
, dropping it meant dropping thechar_p::Box
, that is, freeing the C string that previous pointer was pointing to. But your previous pointer was garbage memory, hence your code reaching a failure when attempting to free it).
from safer_ffi.
I'm going to close the issue since I consider that the usage of the optional Out
type solves your use case. But don't let the closed issue deter you from asking further questions or clarifications! 🙂 In other words, should you encounter any issue when following my indications, you are free to keep asking about it, even if the issue appears to be closed
from safer_ffi.
Thanks for the detailed response, and sorry it took me so long to come up with a feedback. This was indeed very helpful — I did not notice Out
in the documentation. The only problem I'm still having is that the compiler is not smart enough to figure out that I'm not reusing the error
value if .write()
is called in a nested method call.
To illustrate, I have a bunch of methods that may return Option
or Result
, and I want to make it easy for the user to forward their outcome into the FFI error. Ideally, I would want to write something like
#[ffi_export]
fn api_func(error: Option<Out<'_, Error>>) -> Option<usize> {
let x = returns_option().report_error("returns_option() failed", error)?;
let y = returns_result().report_error("returns_result() failed", error)?;
Some(x + y)
}
and report_result()
is something like
impl<T, E: Display> Reportable<T> for Result<T, E> {
fn report_error<'a>(self, message: &'a str, error: Option<Out<'a, Error>>) -> Option<T> {
match self {
Ok(value) => Some(value),
Err(err) => {
write_error(format!("{}: {}", message, err), error);
None
}
}
}
}
where write_error()
consumes error
. Similar implementation for Option
.
The way report_error()
is implemented, error
is consumed on an erroneous variant of Option
or Result
, so theoretically the compiler should be able to deduce that the usage in api_func()
is correct. And indeed it can deduce that if I inline report_error()
. But without the inlining, alas, it complains about error
being used after it's consumed in the first call.
I see two ways out of this, both are not completely satisfactory.
Explicit: make report_error(...) -> Option<(T, Option<Out<'a, Error>)>
. So api_func()
would look like
#[ffi_export]
fn api_func(error: Option<Out<'_, Error>>) -> Option<usize> {
let (x, error) = returns_option().report_error("returns_option() failed", error)?;
let (y, error) = returns_result().report_error("returns_result() failed", error)?;
Some(x + y)
}
This adds visual noise to the users of report_error()
.
Macro: inline repor_error()
every time. So api_func()
would look like
#[ffi_export]
fn api_func(error: Option<Out<'_, Error>>) -> Option<usize> {
let x = report_option!(returns_option(), "returns_option() failed", error);
let y = report_result!(returns_result(), "returns_result() failed", error);
Some(x + y)
}
I consider macros the absolute last resort because they're hard to maintain and debug. And it makes the code too vague.
Any ideas?
from safer_ffi.
Context
So, the way to see Out<'_, T>
is as kind of like a &'_ mut T
, albeit with less compiler magic and thus fewer conveniences here and there. And Option<&'_ mut T>
would have a similar ownership problems here, of not being Copy
, etc.
The usual way to "act as if" &mut T
was kind of copyable is reborrowing, whereby you can "copy" a &'shorter mut T
.
With an Option
, this would be something like:
fn example<T> (mut o: Option<&mut T>)
{
fn consumes_option<T> (_: Option<&mut T>)
{}
- consumes_option(o); /* would cause a `move` error on the next `consumes_option` */
+ consumes_option(o.as_deref_mut());
consumes_option(o);
}
Obviously writing o.as_deref_mut()
at each call remains noisy, but at that point we can thus realize we could move that operation inside the helper function:
- fn consumes_option<T> (o: Option<&mut T>)
+ fn consumes_option_not<T> (o: &mut Option<&mut T>)
-
(and use
o.as_deref_mut()
, the reborrowing operation, inside it) -
In practice, thanks to
match ergonomics
, and the magical auto-reborrowing of the inner&mut
, chances are you'd never really need to explicitly callo.as_deref_mut()
(maybe a.as_mut()
but then inside a closure or whatever the resulting&'short mut &'long mut T
would be flattened to a&'short mut T
).
Well, in the case of Out<'_, T>
, all of the above applies, except for the reborrowing which does not happen implicitly / magically: you can't have a &'short mut Out<'long, T>
magically collapse down to a Out<'short, T>
. You'll need to call .reborrow()
on it explicitly.
All this thus yields:
The fix
#[ffi_export]
fn api_func(mut error: Option<Out<'_, Error>>) -> Option<usize> {
+ ^^^
let x = returns_option().report_error("returns_option() failed", &mut error)?;
+ ^^^^
let y = returns_result().report_error("returns_result() failed", &mut error)?;
+ ^^^^
Some(x + y)
}
- (you could even start the body with
let error = &mut error;
and that way you can skip the&mut
s at each call site)
impl<T, E: Display> Reportable<T> for Result<T, E> {
fn report_error<'a>(self, message: &'a str, error: &mut Option<Out<'a, Error>>) -> Option<T> {
+ ^^^^
match self {
Ok(value) => Some(value),
Err(err) => {
write_error(format!("{}: {}", message, err), error.as_mut().map(Out::reborrow));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
None
}
}
}
}
- (another approach would be to
error.take()
instead oferror.as_mut().map(Out::reborrow())
; both are equivalent when called only once, such as in your case, but would vary in behavior ifwrite_error()
somehow managed to be called multiple times inside a function: withreborrow
, the previousError
would be overwritten with no drop glue being run (potential memory leak!), whereas with.take()
, the second write would be unable to succeed since it would get aNone
).
Aside
I don't think you should be repeating the lifetime parameter 'a
, for both the duration of the borrow of the message: &str
, and for that of the outer Out
parameter. Indeed, with that repetition, and with the new &mut Option
signature, you'll run into messages of the form &format!(…)
being rejected because they "don't live long enough" (messages of the form of "…"
string literals, on the other hand, would still be fine):
- fn report_error<'a>(self, message: &'a str, error: &'_ mut Option<Out<'a, Error>>) -> Option<T> {
+ fn report_error (self, message: &'_ str, error: &'_ mut Option<Out<'_, Error>>) -> Option<T> {
from safer_ffi.
Thanks for the explanation, this makes sense. There's one problem though, a declaration like
#[ffi_export]
fn api_func(mut error: Option<Out<'_, Error>>) -> Option<usize> {
does not compile - I'm getting error
underlined and no rules expected this token in macro call
error (compiles without mut
, but there are obviously other compilation errors telling me to make the parameter mutable). I'm using safer-ffi=0.0.9
, is that fixed in the trunk?
Edit: this can be avoided by having a let mut error = error;
statement, which is actually what I've been doing previously. (my previous solution used an Option<&'a mut Option<repr_c::Box<Error>>>
type, which had some drawbacks, like a double allocation, or a necessity to initialize the error pointer as NULL
on the C side)
Edit2: I've tried this solution, and it seems like it doesn't protect from reusing error
. That is, I can have
let x = returns_option().report_error("returns_option() failed", &mut error);
let y = returns_result().report_error("returns_result() failed", &mut error);
(note the lack of question mark operators), and the compiler will not complain, even though the second call may rewrite the error and leak memory.
from safer_ffi.
The mut error
binding got fixed recently: supported as of 0.0.10
(#111)
from safer_ffi.
@fjarri I've moved the discussion and provided more info over the Discussions/
are of this repo: #113
from safer_ffi.
Related Issues (20)
- Specifying `[[bin]]` with required features should be a required instruction HOT 1
- Quickstart `crate-type` `lib` seems documented too optional
- Trivial example requires `python-headers` feature to build HOT 2
- No `DOXYGEN` def yields incorrect enum definition HOT 1
- Missing `Clone` impl for `repr_c::Box` (and friends?)
- VS code complains non snake case
- Generated Clone implementation triggers clippy lint about being incorrect for a Copy type HOT 4
- Allow disabling dependency on `inventory v0.1` HOT 2
- Macro ambiguity on nightly: `std::cfg_match` vs `crate::utils::macros::cfg_match` HOT 1
- Conditional proc-macros HOT 2
- How does `safer_ffi` avoid aliasing rule problem? HOT 3
- Compatibility with C++ vector HOT 2
- Handle non-panic Tokio task termination
- no_libc feature?
- C# headers are `public`, when `internal` visibility most likely suffices
- No tag or release for v0.1.5 HOT 1
- Feature Request: Generate rust fn and struct declarations instead of C headers HOT 1
- Python example? HOT 1
- Feature request: serde Serialize and Deserialize support for repr_c::String and repr_c::Vec
- Inconsistent MSRV user guide HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from safer_ffi.