Exposing C "enums" through extensions

I’m allowing Ruby to access a C based library through an extension.
I’d like to expose some of the enums that are in the C interface, but
wasn’t sure about the best way to do this.

My first thought was to define them as class variables within the
relevant class. But then, as I discovered, there wasn’t a good way I
could keep external code from modifying the values of those variables.
It would be really bad if someone set the value of the “miter line
join” constant to 7 when the only valid range of values is 0 - 3.

On the “Extending Ruby” web site at
http://www.rubycentral.com/book/, I found the
routine"rb_define_readonly_variable" which sounds pretty good. But, so
far as I can tell, it defines a global variable and my preference would
be to scope the variables inside of a class (I don’t know why… it
just seems “tidier”).

Is there a common mechanism that is followed in other extensions?

Scott

The closest thing to enums would be class constants:

 class MyClass
   THIN_LINE = 1
   THICK_LINE = 2
   MONSTER_LINE = 3
 end

 p MyClass::MONSTER_LINE

(and being constants, you will at least get a warning if someone tries to
reassign them)

Also, you don’t have to expose variables in your classes, just provide
accessor methods (either instance methods or class methods) for settings
values; then you can validate the values yourself.

 class MyClass
   def self.miter_line_join=(x)
     raise "Naughty!" unless (0..3) === x
     @@miter_line_join = x
   end
 end

 MyClass.miter_line_join = 3

Regards,

Brian.

···

On Wed, Aug 13, 2003 at 01:03:14PM +0900, Scott Thompson wrote:

I’m allowing Ruby to access a C based library through an extension.
I’d like to expose some of the enums that are in the C interface, but
wasn’t sure about the best way to do this.

My first thought was to define them as class variables within the
relevant class. But then, as I discovered, there wasn’t a good way I
could keep external code from modifying the values of those variables.
It would be really bad if someone set the value of the “miter line
join” constant to 7 when the only valid range of values is 0 - 3.

I’m allowing Ruby to access a C based library through an extension.
I’d like to expose some of the enums that are in the C interface, but
wasn’t sure about the best way to do this.

My first thought was to define them as class variables within the
relevant class. But then, as I discovered, there wasn’t a good way I
could keep external code from modifying the values of those variables.
It would be really bad if someone set the value of the “miter line
join” constant to 7 when the only valid range of values is 0 - 3.

Can you prevent this in C?
you’ll need to raise an ArgumentError somewhere, I’d say.

On the “Extending Ruby” web site at
http://www.rubycentral.com/book/, I found the
routine"rb_define_readonly_variable" which sounds pretty good. But, so
far as I can tell, it defines a global variable and my preference would
be to scope the variables inside of a class (I don’t know why… it
just seems “tidier”).

rb_define_const() should do the trick. also works for #defines.

hth,
Kero.

···

On Wed, 13 Aug 2003 13:03:14 +0900, Scott Thompson wrote:

This is the practice that RMagick follows. Each attribute accessor that
accepts a constant has code that tests the input argument against the list
of valid values.

That, however, does not prevent somebody from assigning a constant with a
correct value but incorrect name. For example, if APPLES = 3 then
miter_line_join would happily accept APPLES as an argument.

I don’t regard this as a major problem.

···

On Wed, 13 Aug 2003 17:01:03 +0900, Brian Candler wrote:

On Wed, Aug 13, 2003 at 01:03:14PM +0900, Scott Thompson wrote:

I’m allowing Ruby to access a C based library through an extension. I’d
like to expose some of the enums that are in the C interface, but wasn’t
sure about the best way to do this.

My first thought was to define them as class variables within the
relevant class. But then, as I discovered, there wasn’t a good way I
could keep external code from modifying the values of those variables.
It would be really bad if someone set the value of the “miter line join”
constant to 7 when the only valid range of values is 0 - 3.

The closest thing to enums would be class constants:

 class MyClass
   THIN_LINE = 1
   THICK_LINE = 2
   MONSTER_LINE = 3
 end

 p MyClass::MONSTER_LINE

(and being constants, you will at least get a warning if someone tries to
reassign them)

Also, you don’t have to expose variables in your classes, just provide
accessor methods (either instance methods or class methods) for settings
values; then you can validate the values yourself.

 class MyClass
   def self.miter_line_join=(x)
     raise "Naughty!" unless (0..3) === x
     @@miter_line_join = x
   end
 end

 MyClass.miter_line_join = 3

Regards,

Brian.

Nor would I; but if it were a concern you could use symbols instead, and a
hash to map to the correct constant value.

class MyClass
LINES = {
:thin => 1,
:thick => 2,
:monster => 3,
}
def self.set_line_size(x)
@@line_size = LINES || (raise “Bad value”)
end
end
MyClass.set_line_size(:thick)

Regards,

Brian.

···

On Wed, Aug 13, 2003 at 08:46:32PM +0900, Tim Hunter wrote:

This is the practice that RMagick follows. Each attribute accessor that
accepts a constant has code that tests the input argument against the list
of valid values.

That, however, does not prevent somebody from assigning a constant with a
correct value but incorrect name. For example, if APPLES = 3 then
miter_line_join would happily accept APPLES as an argument.

I don’t regard this as a major problem.

An interesting solution.

I prefer to use instances of a particlar class instead, e.g.:

MyEnum = Struct.new(:name, :value)
class MyEnum
def to_s
return name()
end
end

THIN_LINE = MyEnum.new(“THIN” , 0)
THICK_LINE = MyEnum.new(“THICK” , 1)
MONSTER_LINE = MyEnum.new(“MONSTER” , 2)

Then I can write something like:

case line_style
when THIN_LINE then …
when THICK_LINE then …
when MONSTER_LINE then …
end

Or something like:

puts THIN_LINE #=> “THIN”

It’s also possible to add singleton methods to each of these objects to
avoid case/when.

I’ve written a few classes to help me generate these type of enums in
Ruby code and in extensions:

RubyCollections download | SourceForge.net
Exceptional Ruby download | SourceForge.net

The latter has no sample code, but in my extensions I write something
like:

Exc_Ruby::Enum_Wrapper<Line_Style> line_style;

extern “C”
void Init_extension()
{
line_style
.initialize(rb_cObject, “Line_Style”)
.define_value(“THIN_LINE” , THIN_LINE)
.define_value(“THICK_LINE” , THICK_LINE)
.define_value(“MONSTER_LINE” , MONSTER_LINE)
}

And on days when I’m not in the mood to deal with something this
heavyweight, I can always cheat a little and just use instances of Class
instead of instances of a particular enum type:

class THIN_LINE ; end
class THICK_LINE ; end
class MONSTER_LINE ; end

Paul

···

On Wed, Aug 13, 2003 at 08:53:53PM +0900, Brian Candler wrote:

Nor would I; but if it were a concern you could use symbols instead, and a
hash to map to the correct constant value.

class MyClass
LINES = {
:thin => 1,
:thick => 2,
:monster => 3,
}
def self.set_line_size(x)
@@line_size = LINES || (raise “Bad value”)
end
end
MyClass.set_line_size(:thick)

An interesting solution.

I prefer to use instances of a particlar class instead, e.g.:

MyEnum = Struct.new(:name, :value)
class MyEnum
def to_s
return name()
end
end

THIN_LINE = MyEnum.new(“THIN” , 0)
THICK_LINE = MyEnum.new(“THICK” , 1)
MONSTER_LINE = MyEnum.new(“MONSTER” , 2)

Then I can write something like:

case line_style
when THIN_LINE then …
when THICK_LINE then …
when MONSTER_LINE then …
end

This is a common idiom in Java. It’s described pretty nicely in the
book Effective Java Programming Language Guide (a book which I greatly
enjoyed in spite of the fact I have written very, very little Java).
It’s a nice choice in that environment because it plays well with
Java’s stronger typing system. That particular argument don’t make
much sense for Ruby, but by no means does that invalidate the technique.

Unfortunately it also doesn’t really solve the problem I was trying to
avoid. The user of this API can still easily replace THIN_LINE with a
completely different value. Granted they will receive a warning, and
probably deserve what they get, but that’s the thing I was trying to
avoid. Something about non-constant constant’s just rubs me the wrong
way… too many years of C++ conditioning I guess.

In the end, I decided that I can’t protect the user from themselves all
the time. Besides it is much more important to make sure that the API
for the extension is Ruby (and not some bizzarre imitation of Ruby).
Ruby allows you to replace constants, for better or worse, so I made
the enum’s into Fixnum constants.

If anyone wants to change my mind, however, I’m open to other
suggestions.

Scott