Why UnixServer only override #accept? method, but leave #accept not redefined?

Check following defined methods in class hierarchy, check the bold text.

╰─ $ cr run 2.cr
server.all_methods # => {
“UNIXServer” =>
[def initialize(path : String, type : Type = Type::STREAM, backlog : Int = 128),
def initialize(*, fd : Handle, type : Type = Type::STREAM, path : String | ::Nil),
def accept?() : UNIXSocket | ::Nil,
def close(delete = true) : Nil],

“Socket::Server” =>
[def accept() : IO,
def accept(),
def accept?() : IO | ::Nil,
def accept?()],
“UNIXSocket” =>
[def path() : String | ::Nil,
def initialize(path : String, type : Type = Type::STREAM),
def initialize(family : Family, type : Type),
def initialize(*, fd : Handle, type : Type = Type::STREAM, path : String | ::Nil),
def local_address() : Socket::UNIXAddress,
def remote_address() : Socket::UNIXAddress,
def receive()],

“Socket” =>
[def fd(),
def family() : Family,
def type() : Type,
def protocol() : Protocol,
def initialize(family : Family, type : Type, protocol : Protocol = Protocol::IP, blocking),
def initialize(fd, family : Family, type : Type, protocol : Protocol = Protocol::IP, blocking),
def connect(host : String, port : Int, connect_timeout) : Nil,
def connect(addr, timeout) : Nil,
def connect(addr, timeout),
def bind(host : String, port : Int) : Nil,
def bind(port : Int),
def bind(addr : Socket::Address) : Nil,
def listen(backlog : Int = SOMAXCONN) : Nil,
def listen(backlog : Int = SOMAXCONN),
def accept() : Socket,
def accept?() : Socket | ::Nil,

So, when i write code like this:

require "socket"

def handle_client(client)
  message = client.gets
  client.puts message
end

server = UNIXServer.new("/tmp/myapp.sock")
while client = server.accept  # this #accept is Socket::Server#accept
  spawn handle_client(client)
end

Does the behavior same as client = server.accept?(this #accept? is UNIXServer#accept) ?

Thanks

The implementation of Socket#accept is generic and based upon #accept?:

class Socket
  def accept : Socket
    accept? || raise Socket::Error.new("Closed stream")
  end
end

Thus there’s no need to override #accept in specific implementation classes, only #accept? needs to be overridden to create the respective socket type (in this case UNIXSocket).

In fact, when i use #accept, it use #accept defined in src/socket/server.cr

Following is code, although, I don’t really understand what this code is doing:

    abstract def accept : IO

    def accept
      sock = accept   # Why does this code not cause a recursive call?
      begin
        yield sock
      ensure
        sock.close
      end
    end

If assume above sock = accept, which accept is Socket#accept, how the compiler make this distinction?

1 Like

The method def you’re citing is a yielding overload. It’s called as server.accept { |client| handle_client(client) }.

2 Likes

I missing the yield invoke in accept body, so, i consider it is better if we define this method like this?
user can know two method with different signature more easier.

   def accept(&)
      sock = accept 
      begin
        yield sock
      ensure
        sock.close
      end
    end

In fact, one more question, why Crystal not force add the & as method parameter if method body exists a yield?

1 Like

Yeah, I’d like that as well.

Ref: Continue with anonymous block arguments · Issue #8764 · crystal-lang/crystal · GitHub