Can't figure out where this is returning Nil

Sorry for Rust idioms - porting a rust library. When I call the below functioon

did_copy = do_dll_copy(mylib)
return did_copy if did_copy.err?

I am getting an
undefined method.err? for Nil - compile-time is (Err(Vcpkg::LibNotFound) | Ok(Nil) | Nil))

why is this returning nil?

    def do_dll_copy(rlib : Library) : Ok(Nil) | Err(LibNotFound)
      if target_dir = ENV["OUT_DIR"]?
        rlib.found_dlls.each do |file|
          dest_path = Path[target_dir] / file.basename
          File.copy(file, dest_path)
          puts "vcpkg build helper copied file #{file} to #{dest_path}"
        end
        rlib.crystal_metadata << "crystal:rustc-link-search=native=#{target_dir}"
        rlib.crystal_metadata << "crystal:rustc-link-search=#{target_dir}"
        return Ok.new(())
      else
        return Err.new(LibNotFound["Cannot copy dll unless env OUT_DIR is set"])
      end
    rescue e
      return Err.new(LibNotFound.new("Can't copy file to dest_path"))
    end

I don’t suppose you could put together a runnable reproduction of the error you’re getting?

Any chance your implementation of Ok or Err define a self.new method that can return nil under certain circumstances?

I wrote up a quick implementation of those and it compiles and runs fine on my machine. That leads me to believe the problem isn’t in the code you’ve got here, but elsewhere in a dependency. Since the only non-stdlib concepts in here are Ok, Err, Library, and LibNotFound, and of those the only ones you’re using in expressions returned from the method are Ok and Err, I’m leaning in their direction atm.

For example, does your Ok.new (because Ok.new(()) doesn’t pass any arguments) return nil instead of Ok.new(nil)?

def call(mylib)
  did_copy = do_dll_copy(mylib)
  return did_copy if did_copy.err?
end

pp call Library.new

record Ok(T), value : T do
  def self.new
    new nil
  end

  def err?
    false
  end
end

record Err(T), error : T do
  def err?
    true
  end
end

struct Library
  getter crystal_metadata = [] of String

  def found_dlls : Array(Path)
    [] of Path
  end
end

record LibNotFound, msg : String do
  def self.[](msg : String)
    new msg
  end
end

def do_dll_copy(rlib : Library) : Ok(Nil) | Err(LibNotFound)
  if target_dir = ENV["OUT_DIR"]?
    rlib.found_dlls.each do |file|
      dest_path = Path[target_dir] / file.basename
      File.copy(file, dest_path)
      puts "vcpkg build helper copied file #{file} to #{dest_path}"
    end
    rlib.crystal_metadata << "crystal:rustc-link-search=native=#{target_dir}"
    rlib.crystal_metadata << "crystal:rustc-link-search=#{target_dir}"
    return Ok.new(())
  else
    return Err.new(LibNotFound["Cannot copy dll unless env OUT_DIR is set"])
  end
rescue e
  return Err.new(LibNotFound.new("Can't copy file to dest_path"))
end

Ok.new(()) does in fact pass a single positional argument, the inner pair of parentheses being an empty Expressions node that, like an empty def body, evaluates to nil.

2 Likes

Silly mistake - took me awhile. The problem actually is this line which I didn’t copy the whole way
did_copy = do_dll_copy(mylib)
what my code actually had was

did_copy = do_dll_copy(mylib) if copy_dlls?

The if at the end makes it possible to be nil? Silly mistake - Sorry

2 Likes

Oh weird, I could’ve sworn it didn’t work for me when I tried it last night. I must’ve misunderstood a different error I was getting while fleshing out the extra parts.

Admittedly, I’ve never actually tried to write an empty set of parens as an expression in Crystal, so I didn’t even realize it’d compile.