Как я могу получить безопасную ссылку на существующий объект в Nim?

#nim-lang

#nim-lang

Вопрос:

Я написал процедуру Nim, взяв в качестве var параметров два объекта. Каждый из них представляет собой объект с int полем level . Прежде чем приступить к реальной работе процедуры, я хочу упорядочить параметры по порядку, по которому они больше level , поэтому я делаю это:

 proc subroutine(param1: var MyObject, param2: var MyObject) =
  var large: ptr MyObject = param1.addr
  var small: ptr MyObject = param2.addr

  # If in wrong order by level, swap large and small
  if large.level < small.level:
    large = param2.addr
    small = param1.addr

  # The rest of the proc references, only variables large and small, e.g.,
  large.level  = 1
  small.level  = 2
  

Кажется, это работает для моего приложения, но я замечаю, что в документации Nim этот ptr тип называется «небезопасным», и его предлагается использовать только для низкоуровневых операций. Существует «безопасный» ссылочный тип ref , и его рекомендуется использовать ref , если вы действительно не хотите выполнять ручное управление памятью.

Я не хочу выполнять ручное управление памятью, и я хочу, чтобы сборщик мусора Nim обрабатывал память для этих параметров для меня, но я не вижу способа обеспечить безопасность ref для двух параметров.

Я действительно хочу иметь возможность написать алгоритм (который значительно сложнее, чем простой код, который я показал) в терминах переменных large and small , а не param1 and param2 . В противном случае, если я могу ссылаться только на параметры param1 и param2 , не создавая для них эти псевдонимы, мне пришлось бы копировать и вставлять один и тот же алгоритм дважды, чтобы отдельно обрабатывать случаи param1.level < param2.level и param1.level >= param2.level .

Есть ли более идиоматический способ Nim сделать что-то подобное, без использования ptr типа?

Ответ №1:

Вы не можете превратить безопасный объект в небезопасный и наоборот, кроме как выполнив копирование объекта.

Обычные переменные хранятся в стеке и, следовательно, будут уничтожены, когда их область действия функции существует, но ссылка может быть сохранена в глобальной переменной и доступна позже. Если бы это было возможно, чтобы сделать его безопасным, компилятору / языку нужно было бы знать какой-то способ извлечения переменной из стека, чтобы она оставалась действительной после существования ее области видимости, или выполнить копирование вручную за вашей спиной, или какую-то другую волшебную вещь.

Это также причина, по которой небезопасно использовать адрес переменной. Вы не можете гарантировать его срок службы, потому что вы можете сохранить этот адрес где-нибудь еще и попытаться использовать его позже. Однако, с точки зрения гарантий памяти, эти переменные должны быть активными, по крайней мере, на время вашего вызова proc, поэтому должно быть безопасно использовать эти псевдонимы адресов в этом процессе, не беспокоясь.

Тем не менее, вы можете переписать свой код, чтобы использовать промежуточный прокси-процесс, который выполняет проверку и, таким образом, передает правильные переменные в каждом слоте. Промежуточный процесс гарантирует, что одна из переменных всегда будет большой, и вы можете избежать использования небезопасных ссылок:

 type
  MyObject = object
    level: int

proc subroutine_internal(large: var MyObject, small: var MyObject) = 
  assert large.level >= small.level
  large.level  = 1
  small.level  = 2

proc subroutine(param1: var MyObject, param2: var MyObject) =
  if param1.level < param2.level:
    subroutine_internal(param2, param1)
  else:
    subroutine_internal(param1, param2)

proc main() =
  var
    a = MyObject(level: 3)
    b = MyObject(level: 40)

  subroutine(a, b)
  echo a, b

main()
  

Комментарии:

1. Спасибо! Не могли бы вы сказать, что более идиоматичным Nim (или более безопасным / рекомендуемым) является использование вашего решения вместо использования ptr ? Или, возможно, более идиоматично не использовать var параметры так свободно, а вместо этого использовать более функциональный подход, возвращая новые объекты, которые представляют измененные версии параметров?

2. @DaveDoty на самом деле не знал бы, поскольку я не следую никаким тенденциям Nim и не смотрю на то, что делают другие. Я бы предпочел версию, отличную от ptr, с точки зрения безопасности, вы можете вернуться через несколько месяцев, посмотреть на эти указатели и сделать то, что вы не должны были делать при первом написании кода. Nim может быть довольно низкоуровневым и обязательным, поэтому я бы не стал принуждать материал к функциональному стилю, если вы действительно этого не предпочитаете или это дает вам какое-то преимущество.

Ответ №2:

 type
  MyObject = object
    level: int

proc subroutine(param1, param2: var MyObject) =
  if param1.level > param2.level:
    swap(param1, param2)
  echo param1.level
  echo param2.level

var
  p1 = MyObject(level: 7)
  p2 = MyObject(level: 3)

subroutine(p1, p2)
p2.level = 13
subroutine(p1, p2)
  

Для этого случая в Nim есть специальная процедура swap(). Параметры Var передаются внутри как указатели, поэтому при передаче параметров копирование не требуется, и swap должен выполнять копирование как можно более оптимально.

Конечно, могут быть случаи, когда использование объектов ref вместо объектов value может иметь преимущества. Ссылки Nim являются управляемыми указателями. Вы можете найти больше информации в руководствах, и мой находится по адресу http://ssalewski.de/nimprogramming.html#_value_objects_and_references .

Комментарии:

1. Это очень круто, я продолжаю учиться у тебя, @Salewski, но этот код изменяет p1 и p2, поэтому второй результат равен (3,13), когда ожидается, что он будет (7,13)

2. Ну, код делает то, что я ожидал, и то, что, как я думал, было предназначено — какая-то сортировка параметров по полю уровня. Вопрос в том, действительно ли мы можем доверять swap() только для внутренней замены указателей, но не для дорогостоящей копии содержимого данных. Если производительность действительно критична, и мы не доверяем swap(), тогда нам придется протестировать или посмотреть сгенерированную сборку (swap — это процедура компилятора intern magic). Но метод мистера Ханкевича тоже хорош.

3. В конце мне больше не нравится мой собственный ответ, поскольку я не до конца понимаю, что на самом деле задумано. И поскольку оба параметра являются параметрами var, swap() действительно скопирует содержимое, так что, возможно, это не то, что было запрошено, извините. Но почему бы просто не вызвать «if a.level < b.level: p(a, b) else: p (b, a)»? Это было бы похоже на ответ Ханкевича.