Skip to content
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

Feature/#35 error wrapping #39

Merged
merged 2 commits into from
Sep 17, 2024
Merged

Feature/#35 error wrapping #39

merged 2 commits into from
Sep 17, 2024

Conversation

JamesMc86
Copy link
Contributor

Added the ability to wrap the function as in #35 but as a direct function on the error cluster.

@jkneer would love to have some feedback and see if you see any issue with this approach over a macro?

@JamesMc86 JamesMc86 force-pushed the feature/#35-error-wrapping branch from 9d71d10 to 8d0c0c5 Compare September 13, 2024 10:26
@jkneer
Copy link
Contributor

jkneer commented Sep 13, 2024

Hi,

Just moving the parameters into the FnOnce is so much cleaner.
It seems also nice to implement it on the error. But it feels a bit off, like the error is wrapping my function? Hmm

Having the possibility to return something on error seems nice, but I fail to see the advantage. Also, I miss the comfort of having an automatic status reflecting the error status.

So

fn somecall() -> Result<()> {}

#[no_mangle]
pub extern "C" fn app_init(
  error: ErrorClusterPtr
) -> LVStatusCode {
with_lverrorhandling!(&error, 
|| {
// do whatever
somecall()?;
},())
}

This way I had the StatusCode for free. (For the status code I'm fine if it is either LVStatusCode::SUCCESS or something else. With the current implementation as a function, I think I cannot actually have the return value reflect the error code easily?

#[no_mangle]
pub extern "C" fn app_init(
  error: ErrorClusterPtr
) -> LVStatusCode {
   error.wrap_function( InternalError::Misc,
|| {
// do whatever
somecall()?;
}
)
}

If we model the error handling after the LV semantics, the wrap_function should always wrap the full function body. Is there then ever a reason to return a status different from "SUCCESS" or the error code (FAILURE) to the caller?

  1. the DLL node has its own error line, for when the actual DLL call fails
  2. Error on the line: nothing inside the wrapper should be executed. The function status should probably indicate that nothing has been executed. But any value different from SUCCESS should be fine. This will almost never happen, as most of the time the error line should also be wired to the error terminal of the DLL node.
  3. Error happening inside the wrapper: we update the error to reflect that. If some error occured and you want to return a status,

I think I would prefer to get rid of the return_on_err parameter and return the status code of the generated error. I don't see when I would ever want to return something else. So if that ever happens I would prefer to do that as a manual implementation in that case instead of having to think about that return value every single time I use the wrapper.

@JamesMc86
Copy link
Contributor Author

So sometimes I think I want to use the return position for actual data. For example just querying a numeric parameter or calling a constructor:

#[no_mangle]
extern "C" fn layout_new_single(
    mut error_cluster_ptr: ErrorClusterPtr,
    //parameters
) -> *mut LvLayout {
    error_cluster_ptr.wrap_function(std::ptr::null_mut(), || {
        //constructor
    })
}

I think not having this would be restrictive.

I like the idea of returning a status code automatically and perhaps there is an alternate version for that. But then, isn't that just a duplicate of the error cluster data anyway?

@jkneer
Copy link
Contributor

jkneer commented Sep 16, 2024

I like the idea of returning a status code automatically and perhaps there is an alternate version for that. But then, isn't that just a duplicate of the error cluster data anyway?

A return status will probably mostly evaluated as a boolean expression. In most of my use cases just returning a boolean would be fine. Returning the status code achieves the same thing with the added value of carrying some information on the failure.

What if A is an Option, and for Option.is_none() we return the error code?

wrap_function(&mut self: LvErrorPtr, return_on_error: R, F) -> R
where
  F: FnOnce() -> Result<R, E>,  // this is important so we can use the try operator
  E: ToLvError
{
       if self.is_err() {
            return return_on_error;
        }
        match function() {
            Ok(value) => value,
            Err(error) => {
                let _ = error.write_error(self);
                if let Some(ret) = return_on_error {
                    return ret
                } else {
                    return self.code()
                }
            }
        }
}

Hmm thats a problem for the return type.

I'm wondering if it would be better to do it in two steps and use map_err to create the customized return on error.

@JamesMc86
Copy link
Contributor Author

A return status will probably mostly evaluated as a boolean expression. In most of my use cases just returning a boolean would be fine. Returning the status code achieves the same thing with the added value of carrying some information on the failure.

So why would someone want to read this in the return position instead of reading the error cluster? This is what I'm missing.

I'm wondering if it would be better to do it in two steps and use map_err to create the customized return on error.

Yeah the consistent return type is the issue so it can't be the same function. You could have a core one that just returns the result then different actions on the back of that. I feel we could still wrap them in the API to reduce boilerplate (this has already made my project much easier to read) but sounds like we just need a couple of variants.

Added the ability to write a function that follows LabVIEWS error
semantics with the error cluster.

Refs: #35
@JamesMc86 JamesMc86 force-pushed the feature/#35-error-wrapping branch from 8d0c0c5 to 1a68ce4 Compare September 17, 2024 06:12
@JamesMc86
Copy link
Contributor Author

I've added a wrap_return_status method which wraps the return function and returns the status instead. I think this is the most composable option.

You always have to be able to return something in an error in case which limited some other options. We could also have something that returns default.

But I think these cases cover it well, don't love the name but have nothing better right now!

I've been actively using it though and definately makes integration easier

Copy link
Contributor

@jkneer jkneer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Let's merge it in.

@JamesMc86 JamesMc86 merged commit 098b239 into main Sep 17, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants