What is the proper way to return the results of custom module's form submission?
As JimaJamma noted in the comments the search module contains an excellent example of one way to do this. The search_view() function is where the magic happens, this is a custom version of that logic which should help explain the process:

function MYMODULE_menu() {
  $items['query'] = array(
    'title' =&gt; 'Query',
    'access arguments' =&gt; array('access content'),
    'page callback' =&gt; 'MYMODULE_results_page',
  );

  return $items;
}

function MYMODULE_results_page($parameter = NULL) {
  // Even if 'page arguments' isn't defined in a hook_menu() item, Drupal breaks
  // up the URL and passes arguments through to the callback function implicitly.
  // So if the page requested is 'query/123', out item defined at
  // 'query' will be used, and '123' will be passed as the first argument, in
  // this case into the $parameter var.
  //
  // If the parameter is empty, we need to check the GET/POST request to see if
  // the form has been submitted with a new search parameter.
  if (empty($parameter)) {
    if (!empty($_REQUEST['parameter'])) {
      // Build up a new path based on the submitted parameter.
      $path = 'query/' . $_REQUEST['parameter'];

      // And re-direct there. The request will come straight into this function
      // again, but this condition won't fire.
      drupal_goto($path);
    }
  }

  // Now we're in a position to query for results if there's a non-empty
  // parameter. Otherwise we just show the form (like the search page we'll
  // show the form unconditionally).
  $results_build = array('#markup' =&gt; '');
  foreach (MYMODULE_get_results_by_parameter($parameter) as $result) {
    $results_build[] = MYMODULE_build_result_render_array($result);
  }

  $build = array(
    'form' =&gt; drupal_get_form('MYMODULE_query_form'),
    'results' =&gt; $results_build,
  );

  return $build;
}

function MYMODULE_query_form($form, &amp;$form_state) {
  // Make we're submitting to the right path.
  $form['#action'] = 'query';

  $form['parameter'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t('Enter query here'),
  );

  $form['submit'] = array(
    '#type' =&gt; 'submit',
    '#value' =&gt; t('Submit'),
  );

  return $form;
}


I haven't tested that or anything so excuse any small bugs. If there are any bugs hopefully the comments, and the comments in the search_view() function, will do enough to help explain what should be happening :)