-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Result<(),MyCustomError>
instead of anyhow
?
#533
Comments
Good idea! Feel free to make a PR :) This should be possible. flutter_rust_bridge/frb_rust/src/handler.rs Line 320 in 9c57bf6
and flutter_rust_bridge/frb_rust/src/handler.rs Line 271 in 9c57bf6
|
Errors are always translated as exceptions on Dart side, right? I think the best here would be to return Currently with anyhow errors there is no way to catch specific exceptions on the Dart side, which is bad for apps that try to recover from errors (like mine). |
Well I seldomly see dart code using
That sounds pretty reasonable.
Totally agree. If we |
Until we have custom exceptions, I think this might be the only way to preserve errors coming from Rust for now. What we could do is provide a hand-rolled Result in Dart like this: @freezed
class Result<T, E> {
const factory Result.ok(T value) = Ok;
const factory Result.err(E err) = Err;
} so we can have users return a hypothetical pub type DartResult<T, E = ()> = Result<T, E>; I'm not quite sure if this would be more work than supporting custom exceptions by itself. |
I guess custom exceptions are not very hard to implement. Current implementation of "throwing a dart exception from rust exception" is nothing but "transfer a few strings (exception name, message, etc) to dart, and let dart side throw exception". So it may be easy to transfer an extra field indicating which exception is happening. Or, we can just transfer a normal struct from rust to dart and utilize existing infra. It is only after it goes to the dart side that we throw it instead of return it. |
better to transfer the type cause errors could be nested with other errors, but I don't know how nesting would work on dart side
…-------- Mensagem Original --------
Em 15 de jul. de 2022 00:58, fzyzcjy escreveu:
I guess custom exceptions are not very hard to implement. Current implementation of "throwing a dart exception from rust exception" is nothing but "transfer a few strings (exception name, message, etc) to dart, and let dart side throw exception". So it may be easy to transfer an extra field indicating which exception is happening.
Or, we can just transfer a normal struct from rust to dart and utilize existing infra. It is only after it goes to the dart side that we throw it instead of return it.
—
Reply to this email directly, [view it on GitHub](#533 (comment)), or [unsubscribe](https://github.com/notifications/unsubscribe-auth/ABSTHAKYAWP75FFIC55CL7DVUDOVZANCNFSM52CHUSLQ).
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
and any arbitrary field, etc :)
I guess nothing special. frb already supports nesting structs, and the error will be nothing but another struct. |
I have an idea: we can have a specialization for
Provided these conditions, we can have a new Dart class that extends |
@Desdaemon are specializations like this already possible in Rust? Anyways, I'm beggining to take a look on the error handler and how to do this |
I guess he means we can do such checking at our code generator. |
Rough directions: Firstly, errors are captured at https://github.com/fzyzcjy/flutter_rust_bridge/blob/master/frb_rust/src/handler.rs#L119 and naively handled at flutter_rust_bridge/frb_rust/src/handler.rs Line 320 in e841421
Then comes the core error handling:
The current approach is very very naive. Basically, when we want to return a value, or send a value via |
Therefore, the solution may be quite simple: Instead of posting Then, in Dart side, indeed almost nothing to do. Just mimic how we receive the normal result and deserialize it, we can deserialize the error data, and throw it. One minor thing may be that, we may need to let our custom exception to |
I've just read how Isolate posting works and now I understand it better. Yes, if But here: impl<E: Executor, EH: ErrorHandler> Handler for SimpleHandler<E, EH> {
fn wrap<PrepareFn, TaskFn, TaskRet>(&self, wrap_info: WrapInfo, prepare: PrepareFn)
where
PrepareFn: FnOnce() -> TaskFn + UnwindSafe,
TaskFn: FnOnce(TaskCallback) -> Result<TaskRet> + Send + UnwindSafe + 'static,
TaskRet: IntoDart,
{ the /// Errors that occur from normal code execution.
#[derive(Debug)]
pub enum Error {
/// Errors from an [anyhow::Error].
ResultError(anyhow::Error), // <------------------ PS: why not used?
/// Exceptional errors from panicking.
Panic(Box<dyn Any + Send>),
/// Custom errors that implement `IntoDar`
CustomError(Box<dyn IntoDart + Send>),
} Then: impl<E: Executor, EH: ErrorHandler> Handler for SimpleHandler<E, EH> {
fn wrap<PrepareFn, TaskFn, TaskRet>(&self, wrap_info: WrapInfo, prepare: PrepareFn)
where
PrepareFn: FnOnce() -> TaskFn + UnwindSafe,
TaskFn: FnOnce(TaskCallback) -> std::Result<TaskRet, E> + Send + UnwindSafe + 'static,
TaskRet: IntoDart,
E: IntoDart
{
let _ = panic::catch_unwind(move || {
let wrap_info2 = wrap_info.clone();
if let Err(error) = panic::catch_unwind(move || {
let task = prepare();
self.executor.execute(wrap_info2, task);
}) {
self.error_handler
.handle_error(wrap_info.port.unwrap(), Error::CustomError(error));
}
});
} Also we need impl ErrorHandler for ReportDartErrorHandler {
fn handle_error(&self, port: i64, error: Error) {
Rust2Dart::new(port).custom_error(error.code().to_string(), error.message());
}
fn handle_error_sync(&self, error: Error) -> Vec<u8> {
//...
}
} where /// Send a custom error message back to the specified port.
pub fn custom_error<T: IntoDart>(&self, error: T) -> bool {
self.isolate.post(vec![
RUST2DART_ACTION_CUSTOM_ERROR.into_dart(),
error.into_dart(),
])
} and then do some deserialization on Dart for the new case Is this approach ok? |
Sure, feel free to change APIs as that is necessary. But I guess you need
If we are doing CustomError, shall we remove this old error? Since we may directly let anyhow::Error be IntoDart and then it is nothing more special.
Similarly, what about make anyhow::Error nothing special, but just like any other errors. |
Just did this: pub enum Error {
/// Errors that implement [IntoDart].
CustomError(Box<dyn IntoDart>),
/// Exceptional errors from panicking.
Panic(Box<dyn Any + Send>),
}
pub trait Executor: RefUnwindSafe {
/// Executes a Rust function and transforms its return value into a Dart-compatible
/// value, i.e. types that implement [`IntoDart`].
fn execute<TaskFn, TaskRet, Er>(&self, wrap_info: WrapInfo, task: TaskFn)
where
TaskFn: FnOnce(TaskCallback) -> Result<TaskRet, Er> + Send + UnwindSafe + 'static,
TaskRet: IntoDart,
Er: IntoDart + 'static;
//... and ran on the example pub enum CustomError{
Error1(String),
Error2(u32),
Error3(i32)
}
pub fn return_custom_error() -> Result<u32, CustomError> {
Err(CustomError::Error2(3))
} which generated #[no_mangle]
pub extern "C" fn wire_return_custom_error(port_: i64) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap(
WrapInfo {
debug_name: "return_custom_error",
port: Some(port_),
mode: FfiCallMode::Normal,
},
move || move |task_callback| return_custom_error(),
)
} but the return type of
What do you think of creating another field on pub struct IrFunc {
pub name: String,
pub inputs: Vec<IrField>,
pub output: IrType,
pub error_output: Option<IrType>, // <------- this
pub fallible: bool,
pub mode: IrFuncMode,
pub comments: Vec<IrComment>,
} so I can represent the output error? Then later I can visit it and generate its |
Sure. And then when traversing all types, treat error_output just almost the same as output and inputs. |
Shall we remove fallible (and make it a method instead), since "error_output!=none" is equivalent to fallible==true? |
Result<(),MyCustomError>
instead of anyhow
?
It's more complicated than we thought. Here, a pub fn try_from_syn_type(ty: &syn::Type) -> Option<Self> {
match ty {
syn::Type::Path(syn::TypePath { path, .. }) => {
let last_segment = path.segments.last().unwrap().clone();
match last_segment.arguments {
syn::PathArguments::None => Some(SupportedInnerType::Path(SupportedPathType {
ident: last_segment.ident,
generic: None,
})),
syn::PathArguments::AngleBracketed(a) => { and I can only convert to one of these: pub enum SupportedInnerType {
/// Path types with up to 1 generic type argument on the final segment. All segments before
/// the last segment are ignored. The generic type argument must also be a valid
/// `SupportedInnerType`.
Path(SupportedPathType),
/// Array type
Array(Box<Self>, usize),
/// The unit type `()`.
Unit,
} the concept of pub enum SupportedInnerType {
/// Path types with up to 1 generic type argument on the final segment. All segments before
/// the last segment are ignored. The generic type argument must also be a valid
/// `SupportedInnerType`.
Path(SupportedPathType),
/// Path types with up to n generic type argument on the final segment.
/// The generic type argument must also be a valid `SupportedInnerType`.
PathMultiple(Vec<SupportedPathType>),
/// Array type
Array(Box<Self>, usize),
/// The unit type `()`.
Unit,
} but realized it does not make sense to have a What should I do here? Remember that this is just so the |
I'm trying with #[derive(Debug)]
pub struct SupportedPathType {
pub ident: syn::Ident,
pub generic: Vec<Box<SupportedInnerType>>,
} instead of #[derive(Debug)]
pub struct SupportedPathType {
pub ident: syn::Ident,
pub generic: Option<Box<SupportedInnerType>>,
} |
Well, #[derive(Debug)]
pub struct SupportedPathType {
pub ident: syn::Ident,
pub generic: Vec<Box<SupportedInnerType>>,
} makes it really hard to /// Converts a path type into an `IrType` if possible.
pub fn convert_path_to_ir_type(&mut self, p: SupportedPathType) -> Vec<IrType> { I don't know what to do |
What about storing error type in a separate field? Just like what you mentioned:
Then, |
in order to get the pub fn try_from_syn_type(ty: &syn::Type) -> Option<Self> {
match ty {
syn::Type::Path(syn::TypePath { path, .. }) => {
let last_segment = path.segments.last().unwrap().clone();
match last_segment.arguments {
syn::PathArguments::None => Some(SupportedInnerType::Path(SupportedPathType {
ident: last_segment.ident,
generic: None,
})),
// This part gets only the first argument, the T. To get the E, lots of work need to be done :(
syn::PathArguments::AngleBracketed(a) => { |
Shall we do something like |
I tried exactly that, but the recursiveness of |
Ah I see. What about
|
wow I tried exactly that afterwards: lattice0@6f5a6fb but it was really hard to create the vec for all parsed types, because now I should do |
wow we have had the same thoughts :)
Could you please elaborate a bit? If it is nothing but adding |
Because then |
Hmm maybe we can use the only element from the Vec when appropriate? |
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue. |
(Reopen given #1325) |
This library is very useful, but having to return a text error instead of a nice struct with custom error information would be much nicer! Is it possible? Is it in the plans?
The text was updated successfully, but these errors were encountered: