ククログ

株式会社クリアコード > ククログ > 名古屋Ruby会議03:Apache ArrowのRubyバインディング(6) #nagoyark03

名古屋Ruby会議03:Apache ArrowのRubyバインディング(6) #nagoyark03

前回はGObject Introspectionでのオブジェクトの寿命の扱いについて説明しました。今回は戻り値のオブジェクトの寿命について説明します。実際に動くバインディングはkou/arrow-glibにあります。

戻り値のオブジェクトの寿命

一般的に戻り値のオブジェクトの寿命には次のパターンがあります。

  • 所有権は関数実行側にあるパターン:戻り値のオブジェクトが必要なくなったら戻り値を受け取った側が責任を持って解放する。

  • 所有権は関数側にあるパターン:戻り値を受け取った側は解放してはいけない。

話を単純にするために、戻り値のオブジェクトとして文字列(char *)を考えてみましょう。

strdup(3)(引数の文字列をコピーして返す関数)は前者(所有権は関数実行側にあるパターン)です。strdup(3)の戻り値はstrdup(3)を呼び出した側が責任を持って解放しなければいけません。

char *copy;
copy = strdup("Hello");
free(copy); /* 呼び出し側がfree()する。 */

getenv(3)は後者(所有権は関数側にあるパターン)です。getenv(3)の戻り値はgetenv(3)を呼び出した側が解放してはいけません

char *path;
path = getenv("PATH");
/* free(path); */ /* 呼び出し側がfree()してはダメ! */

これが基本です。

ただし、前者の「所有権は関数実行側にあるパターン」にはもう少しパターンがあります。それは戻り値のオブジェクトがコンテナーの場合です。コンテナーというのは配列・リスト・ハッシュテーブルなどのように複数のオブジェクトを格納するオブジェクトのことです。

コンテナーの場合は、次のパターンがあります。

  • コンテナーの所有権のみ関数実行側にあるパターン

  • コンテナーの所有権もコンテナーの中のオブジェクトの所有権も関数実行側にあるパターン

コンテナーの所有権のみの場合はコンテナーのみを解放します。

GObject Introspectionでの戻り値のオブジェクトの寿命の指定

GObject Introspectionではこれらのそれぞれのパターンを指定できます。指定すると後はGObject Introspection(を使ったバインディング、Rubyならgobject-introspection gem)が適切に寿命を管理してくれます。

パターンはそれぞれの関数のドキュメントで指定します。

次の関数は新しくGArrowArrayオブジェクトを作って返す関数です。新しく作ったオブジェクトの所有権は関数呼び出し側にあります。そのため、「所有権は関数実行側にあるパターン」です。

/**
 * garrow_array_builder_finish:
 * @builder: A #GArrowArrayBuilder.
 *
 * Returns: (transfer full): The built #GArrowArray.
 */
GArrowArray *
garrow_array_builder_finish(GArrowArrayBuilder *builder)
{
  auto arrow_builder = garrow_array_builder_get_raw(builder);
  std::shared_ptr<arrow::Array> arrow_array;
  arrow_builder->Finish(&arrow_array);
  return garrow_array_new_raw(&arrow_array);
}

ポイントは次の箇所です。Returns:が戻り値に関する情報のドキュメントという意味で、(transfer full)が「所有権は呼び出し側にある」という意味です。

/**
 * Returns: (transfer full): The built #GArrowArray.
 */

「所有権は関数側にあるパターン」も見てみましょう。

次の関数はフィールド名を返す関数です。フィールド名はフィールドオブジェクトに所有権があるので関数呼び出し側は解放してはいけません。(gcharはGLibが提供しているchar型です。charと同じです。)

/**
 * garrow_field_get_name:
 * @field: A #GArrowField.
 *
 * Returns: The name of the field.
 */
const gchar *
garrow_field_get_name(GArrowField *field)
{
  const auto arrow_field = garrow_field_get_raw(field);
  return arrow_field->name.c_str();
}

ポイントは次の箇所です。戻り値の型にconstがついている場合はGObject Introspectionは所有権は関数側にあると推測します。そのため、明示的にReturns: (transfer none):と書く必要はありません。

const gchar *

「コンテナーの所有権のみ関数実行側にあるパターン」も見てみましょう。

arrow-glibでは使っていないのでGTK+にある関数を持ってきました。

/**
 * gtk_print_backend_load_modules:
 *
 * Returns: (element-type GtkPrintBackend) (transfer container):
 */
GList *
gtk_print_backend_load_modules (void)
{
  /* ... */
}

ポイントは(transfer container)です。これでコンテナーの所有権のみ関数呼び出し側にある、ということを指定します。

(element-type GtkPrintBackend)はコンテナー内の要素の型を指定しています。)

最後に「コンテナーの所有権もコンテナーの中のオブジェクトの所有権も関数実行側にあるパターン」を見てみましょう。

/**
 * garrow_record_batch_get_columns:
 * @record_batch: A #GArrowRecordBatch.
 *
 * Returns: (element-type GArrowArray) (transfer full):
 *   The columns in the record batch.
 */
GList *
garrow_record_batch_get_columns(GArrowRecordBatch *record_batch)
{
  const auto arrow_record_batch = garrow_record_batch_get_raw(record_batch);

  GList *columns = NULL;
  for (auto arrow_column : arrow_record_batch->columns()) {
    GArrowArray *column = garrow_array_new_raw(&arrow_column);
    columns = g_list_prepend(columns, column);
  }

  return g_list_reverse(columns);
}

ポイントは(transfer full)です。(transfer full)はコンテナーなオブジェクトにもそうでないオブジェクトにも使えるということです。

まとめ

GObject Introspectionでの戻り値のオブジェクトの寿命について説明しました。次回はインターフェイスについて説明します。