2

What's the right way to evaluate nil returned values from Go functions in Android Java?

Here's what I've tried:

// ExportedGoFunction returns a pointer to a GoStruct or nil in case of fail
func ExportedGoFunction() *GoStruct {
  return nil
}

Then I generate a .aar file via gomobile using:

gomobile bind -v --target=android

In my Java code I tried to evaluate nil as null but it's not working. Java Code:

GoLibrary.GoStruct goStruct = GoLibrary.ExportedGoFunction();
if (goStruct != null) {
   // This block should not be executed, but it is
   Log.d("GoLog", "goStruct is not null");
}

Disclaimer: Other methods from the go library work flawless

saurabh
  • 724
  • 3
  • 17
Bruno Vieira
  • 3,884
  • 1
  • 23
  • 35
  • Go's `nil` is a typed value, so I don't think it translates directly to Java's `null`. You might need to check it against the zero value of `GoStruct`. – SnoProblem Sep 18 '15 at 00:33
  • Yes, I tried that and then the app crashed mumbling something about improper pointer de-reference. BTW, if a struct is nil in go and you try to access it's fields, go also crashes, so testing GoStruct for Zero values is (literally) a no go for Java – Bruno Vieira Sep 18 '15 at 00:37
  • Hmmm, I was afraid that would happen. I'm not sure how the bind is casting nil. A really hacky solution would be a try/catch block to test for nil by forcing the exception. And, yes I do feel dirty even suggesting it. – SnoProblem Sep 18 '15 at 00:44
  • Actually that's what I came up doing. And then I wrapped all my code in yet another class to handle all my go functions just to avoid try/catch blocks, causing yet more overhead. I guess the folks at gomobile should have a very good reason to not map nil to null, I'm just failing to see it – Bruno Vieira Sep 18 '15 at 00:51

2 Answers2

2

For possible future reference, as of 09/2015 I've come up with two ways of dealing with the problem.

The first one is to return an error from the Go code and try/catch-ing the error in Java. Here's an example:

// ExportedGoFunction returns a pointer to a GoStruct or nil in case of fail
func ExportedGoFunction() (*GoStruct, error) {
   result := myUnexportedGoStruct()
   if result == nil {
      return nil, errors.New("Error: GoStruct is Nil")
   }

   return result, nil
}

And then try/catch the error in Java

try {
   GoLibrary.GoStruct myStruct = GoLibrary.ExportedGoFunction();
} 
catch (Exception e) {
   e.printStackTrace(); // myStruct is nil   
}

This approach is both idiomatic Go and Java, but even if it works on preventing the program to crash, it ends up bloating the code with try/catch statements and causing yet more overhead.

So, based on user @SnoProblem answers the non-idiomatic way of solving it and properly handle null values I came up with was:

// NullGoStruct returns false if value is nil or true otherwise
func NullGoStruct(value *GoStruct) bool {
    return (value == nil) 
}

And then check the code in Java like:

GoLibrary.GoStruct value = GoLibrary.ExportedGoFunction();
if (GoLibrary.NullGoStruct(value)) {
   // This block is executed only if value has nil value in Go
   Log.d("GoLog", "value is null");
}
Community
  • 1
  • 1
Bruno Vieira
  • 3,884
  • 1
  • 23
  • 35
1

Looking through the testing package for go mobile, it looks like you need to cast the null value to the type.

From the SeqTest.java file:

 public void testNilErr() throws Exception {
    Testpkg.Err(null); // returns nil, no exception
  }

Edit: A non-exception example, too:

byte[] got = Testpkg.BytesAppend(null, null);
assertEquals("Bytes(null+null) should match", (byte[])null, got);
got = Testpkg.BytesAppend(new byte[0], new byte[0]);
assertEquals("Bytes(empty+empty) should match", (byte[])null, got);

It may be as simple as:

GoLibrary.GoStruct goStruct = GoLibrary.ExportedGoFunction();
if (goStruct != (GoLibrary.GoStruct)null) {
   // This block should not be executed, but it is
   Log.d("GoLog", "goStruct is not null");
}

EDIT: Suggestion for utility method:

You could add a utility function to the library to give you the typed nil value.

func NullVal() *GoStruct {
    return nil
}

Still a bit hacky, but it should be less overhead than multiple wrappers and exception handling.

SnoProblem
  • 1,909
  • 13
  • 14
  • I upvoted the question but somehow it doesn't work. I guess I'll have to keep try/catch -ing nil values. – Bruno Vieira Sep 18 '15 at 01:35
  • 1
    You could add a function `GoLibrary.NullVal()` that returns a typed `nil` value. – SnoProblem Sep 18 '15 at 02:23
  • There's no way to have a Struct as a return type when generating a .aar file via gomobile bind. Pointers to struct however work. That's why I'm returning a pointer to my GoStruct in my ExportedGoFunction(). Also, by using NullVal() I would end with something that's not idiomatic Go nor idiomatic Java and lose the flexibility of pointers. – Bruno Vieira Sep 18 '15 at 02:31
  • Sorry, I meant that to return a pointer rather than a struct. I'll fix that in the code. I don't mean to suggest that this is idiomatic in either language, but it's likely a more efficient alternative to wrappers and exception handling. – SnoProblem Sep 18 '15 at 02:50