# Slim syntax for struct values

**URL:** <https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294>\
**Category:** Help & Support\
**Created:** [October 11, 2024, 5:42am UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294 "2024-10-11T05:42:14Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![chroma9129](https://avatars.discourse-cdn.com/v4/letter/c/c2a13f/32.png) [@chroma9129](https://forum.crystal-lang.org/u/chroma9129)\
**Post date:** [October 11, 2024, 5:42am UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/1 "2024-10-11T05:42:14Z")

</div>

When passing struct values for arguments, is there a slimmer syntax than `Point.new(3, 4)`? For example, C++ supports `{3, 4}`. I tried parens, brackets and curlies.

If not, can I register an “automatic converter” from one type (`Tuple(Int32, Int32)`) to another (`Point`)?

---

<div class="post-metadata">

**Author:** ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)\
**Post date:** [October 11, 2024, 7:22am UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/2 "2024-10-11T07:22:39Z")

</div>

If you can show us some more code, we may be able to answer your question more effectively.

C++ and Crystal are different languages and different ways of thinking. I would love to answer your question, but I don’t think what you really need here is an automatic converter. I don’t know what you really need.

---

<div class="post-metadata">

**Author:** ![naqvis](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/naqvis/32/906_2.png) [@naqvis](https://forum.crystal-lang.org/u/naqvis)\
**Post date:** [October 11, 2024, 7:55am UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/3 "2024-10-11T07:55:06Z")

</div>

Crystal supports [Hash-like Type literal](https://crystal-lang.org/reference/1.14/syntax_and_semantics/literals/hash.html#hash-like-type-literal) for hashes and hash-like types.

This requires class to have argument-less constructor and implement `[]=` operator.

But **TIL** that one can override `<<` operator to achieve Tuple Like literal.

Given below sample code (assuming default values for types). It overrides both `[]=` and `<<` operators to achieve hash-like and tuple like functionality

[https://carc.in/#/r/had3](https://carc.in/#/r/had3)

```crystal
struct Foo
  getter foo : String
  getter bar : Int32
  getter baz : Float64

  def initialize(@foo, @bar, @baz)
  end

  def self.new
    new("", 0, 0.0)
  end

  def <<(value)
    case value
    when String then @foo = value
    when Int32 then @bar = value
    when Float64 then @baz = value
    end
  end

  def []=(name, value)
    case name
    when "foo" then @foo = value.to_s
    when "bar" then @bar = value.to_i
    when "baz" then @baz = value.to_f
    else
      raise ArgumentError.new("invalid argument")
    end
  end
end

```

with above code you can create instance in different ways as well

```crystal
foo = Foo{"foo" => "Hi", "bar" => 12, "baz" => 14.0}
# OR
bar = Foo{"Hi", 12, 14.0}

```

HIH

//Ali

---

<div class="post-metadata">

**Author:** ![Sunrise](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/sunrise/32/2339_2.png) [@Sunrise](https://forum.crystal-lang.org/u/Sunrise)\
**Post date:** [October 11, 2024, 8:30am UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/4 "2024-10-11T08:30:27Z")

</div>

As for the automatic converter you mentioned, I think you might be referring to function overloading, like this

```crystal
struct Point
  property x : Int32
  property y : Int32

  def initialize(@x, @y)
  end

  def self.new(p : Tuple(Int32, Int32))
    new(p[0], p[1])
  end

  def self.[](px : Int32, py : Int32)
    new(px, py)
  end
end

struct Tuple
  def to_p : Point
    if self.is_a?(Tuple(Int32, Int32))
      Point.new(self)
    else
      raise "to_p can only be called on Tuple(Int32, Int32)"
    end
  end
end

tu = {3, 4}
pp Point.new(tu) # => Point(@x=3, @y=4)
pp Point[3, 4] # => Point(@x=3, @y=4)
pp tu.to_p # => Point(@x=3, @y=4)

```

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [October 11, 2024, 12:51pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/5 "2024-10-11T12:51:09Z")

</div>

I think all of these solutions so far are way more complicated than you need. The gist of it is no, there isn’t a slim syntax like this natively. I’d really just consider typing the few extra chars and just use `.new`. If you have a tuple you can even just do like `Point.new *{1, 2}` and it would work the same as `Point.new 1, 2` versus handling `Tuple` itself.

If you _really_ want something similar, you could define a `macro [](*args)` and have it expand to something like `Point.new *args`. But ultimately unless you have a good reason to, I’d just keep it simple.

Full code:

```crystal
struct Point
  getter x : Int32
  getter y : Int32

  macro [](*args)
    {{@type}}.new {{args.splat}}
  end

  def initialize(@x, @y)
  end
end

pp Point.new 1, 2 # => Point(@x=1, @y=2)
pp Point.new *{3, 4} # => Point(@x=3, @y=4)
pp Point[5, 6] # => Point(@x=5, @y=6)

```

---

<div class="post-metadata">

**Author:** ![chroma9129](https://avatars.discourse-cdn.com/v4/letter/c/c2a13f/32.png) [@chroma9129](https://forum.crystal-lang.org/u/chroma9129)\
**Post date:** [October 11, 2024, 1:56pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/6 "2024-10-11T13:56:45Z")

</div>

Thank you for all the responses. @kojix2 had asked for code he’s correct that I should have provided an example:

```cpp
/*
C++ example of slim syntax for struct literals.
Comes in handy for small things like Point, Color, etc.
compile:
    clang++ -std=c++23 -Wall -Wextra -Werror -o point point.cpp
run:
    ./point
*/

#include <format>

struct Point {
    int x, y;
};

int distance_squared(Point a, Point b) {
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return dx * dx + dy * dy;
}

int main() {
    // slim syntax used twice in func call arguments:
    int ds = distance_squared({1, 2}, {3, 4});
    std::printf("d: %d\n", ds);
    return 0;
}

```

---

<div class="post-metadata">

**Author:** ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)\
**Post date:** [October 11, 2024, 2:36pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/7 "2024-10-11T14:36:41Z")

</div>

Many people write good answers, but I prefer writing code clearly, like `Point.new(1, 2)`.

However, if you really want to shorten it, `Point[x, y]` is fine.

I’m not a big fan of the `record` macro, but it does make the code look shorter.

```crystal
record Point, x : Int32, y : Int32 do
  def self.[](x, y)
    new x, y
  end
end

def distance_squared(a, b)
  (a.x - b.x) **2 + (a.y - b.y)** 2
end

p! distance_squared(Point[1, 2], Point[3, 4])

```

---

<div class="post-metadata">

**Author:** ![beta-ziliani](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/beta-ziliani/32/1821_2.png) [@beta-ziliani](https://forum.crystal-lang.org/u/beta-ziliani)\
**Post date:** [October 11, 2024, 8:55pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/8 "2024-10-11T20:55:27Z")

</div>

In C++ that syntax works because the type of the arguments is known beforehand. So when saying `distance_squared({1, 2}, ...)`, the compiler knows that `{1, 2}` is in the position of a `Point`, and can make the conversion. (BTW, if you overload `distance_squared`, then it doesn’t work precisely because it doesn’t know anymore the type).

In Crystal types are not mandatory, so in order for it to work, it should do as C++ and only work if there’s no overload of the function/method, and push the conversion until the type is known. Likely doable, but bad for error messages if something doesn’t work.

---

<div class="post-metadata">

**Author:** ![chroma9129](https://avatars.discourse-cdn.com/v4/letter/c/c2a13f/32.png) [@chroma9129](https://forum.crystal-lang.org/u/chroma9129)\
**Post date:** [October 11, 2024, 9:30pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/9 "2024-10-11T21:30:50Z")

</div>

I agree that the C++ code works due to static typing, which Crystal has as well. I’m not suggesting that `{x, y}` should/would work in a situation that was ambiguous or untyped.

The C++ code still works when overloading the function:

```cpp
/*
C++ example of slim syntax for struct literals.
Comes in handy for small things like Point, Color, etc.
compile:
    clang++ -std=c++23 -Wall -Wextra -Werror -o point point.cpp
run:
    ./point
*/

#include <format>

struct Point {
    int x, y;
};

int distance_squared(Point a, Point b) {
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return dx * dx + dy * dy;
}

int distance_squared(int x1, int y1, int x2, int y2) {
	return distance_squared({x1, y1}, {x2, y2});
}

int main() {
    // slim syntax used twice in func call arguments:
    int ds = distance_squared({1, 2}, {3, 4});
    std::printf("d: %d\n", ds);
    ds = distance_squared(1, 2, 3, 4);
    std::printf("d: %d\n", ds);
    return 0;
}
// output
// d: 8
// d: 8

```

It only fails when the overloads are ambiguous. For example, using a `std::pair<>` argument type can trigger `call is ambiguous` which I have no issue with. In practice, this hardly happens, but when it does, the API needs a rethink.

Btw both C++ and Nim support automatic conversion between types which has come in handy.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [October 14, 2024, 5:27am UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/10 "2024-10-14T05:27:33Z")

</div>

> [@kojix2](#):
>
> I’m not a big fan of the `record` macro, but it does make the code look shorter.

I also think so, i consider `record` is one of a few `BAD PART` in the Crystal, it added unnecessary complexity.

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [October 14, 2024, 12:30pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/11 "2024-10-14T12:30:40Z")

</div>

Why? What’s so complex/bad about it? It’s just a struct with getters.

---

<div class="post-metadata">

**Author:** ![ysbaddaden](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ysbaddaden/32/2252_2.png) [@ysbaddaden](https://forum.crystal-lang.org/u/ysbaddaden)\
**Post date:** [October 14, 2024, 3:35pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/12 "2024-10-14T15:35:09Z")

</div>

@Blacksmoke16 It looks nice at first but then you’d like methods, so you start extending it with a weird syntax (a block to add methods?) then you want more attributes and it starts being cramped on that line… so in the end you just rewrite as a `struct` but you loose the methods that record injected…

I never use `record` in any real code. I just directly create a `struct Point`.

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [October 14, 2024, 7:20pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/13 "2024-10-14T19:20:37Z")

</div>

I wouldn’t call it “bad”, but it’s definitely not as clean as I’d like it to be. A quick rundown of how I think about it.

A regular struct might look something like this:

```crystal
struct Message
  getter id : UUID
  getter content : String
  getter metadata : Metadata { Metadata.new }
  getter sent_at : Time
  getter processed_at : Time?

  def initialize(@id, @content, @metadata, @sent_at, @processed_at = nil)
  end

  def processed?
    !processed_at.nil?
  end
end

```

The `getter`s and the args to `initialize` are duplicated and, in most cases, must change in lockstep. There’s no single source of truth for the object’s state and the initialization of that state, so people reach for `record` because it does exactly that:

```crystal
record Message, id : UUID, content : String, sent_at : Time, metadata : Metadata = Metadata.new, processed_at : Time? = nil do
  def processed?
    !processed_at.nil?
  end
end

```

I lose the benefit of the lazy generation of the `metadata` ivar because the parser doesn’t parse blocks that way.

And, as @ysbaddaden mentioned, the attributes are too crammed onto a single line, so then you rewrite like this:

```crystal
record Message,
  id : UUID,
  content : String,
  sent_at : Time,
  metadata : Metadata = Metadata.new,
  processed_at : Time? = nil do
  def processed?
    !processed_at.nil?
  end
end

```

Now you’ve lost the visual boundary between the getters and regular methods. In regular structs, I use that boundary to indicate “this is where you can look to see what state an object has” and `crystal tool format` also creates that boundary for me. But with this structure, even if you put an empty line in between, the formatter removes it.

So that’s annoying, so I change my structure to this:

```crystal
record(
  Message,
  id : UUID,
  content : String,
  sent_at : Time,
  metadata : Metadata = Metadata.new,
  processed_at : Time? = nil,
) do
  def processed?
    !processed_at.nil?
  end
end

```

This adds that clear boundary between state and behavior, but sacrifices other aesthetics and I wouldn’t call this more readable.

For the 🕶 record ([YEAAAAAAAAH!](https://www.youtube.com/watch?v=7uW47jWLMiY)), I don’t think the `record` macro is the right answer to defining `initialize` and `getter`s in a single place for structs with behavior. It’s amazing for objects that are simply used for holding state. It’s right there in the name — “record” is also the term many FP languages use for types of state. However, it’s the only thing we’ve got right now. It would be really nice if we had something better.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [October 29, 2024, 4:31pm UTC](https://forum.crystal-lang.org/t/slim-syntax-for-struct-values/7294/14 "2024-10-29T16:31:58Z")

</div>

> [@jgaskins](#):
>
> ```auto
> record(
> Message,
> id : UUID,
> content : String,
> sent_at : Time,
> metadata : Metadata = Metadata.new,
> processed_at : Time? = nil,
> ) do
> def processed?
> !processed_at.nil?
> end
> end
> 
> ```

Write Message with record in one line is better, because when filter by the record name use grep, you want to know, the `Message` is a record.

```auto
record(Message,
  id: UUID
  # ...
  ) do
   # ....
end

```

* * *

Another disadvantage is:

When write record use struct directly, you can jump to where the definition of this struct if mouse over the struct name when enable LSP support (crystalline) use `find definition`. but record macro is not.
